Fractal Project Structure with React and Redux - pros/cons [closed] - javascript

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 4 years ago.
Improve this question
I would like to know what are the pros and cons of using a Fractal Structure in a React + Redux project and I was wondering if anyone has any experience with this approach or if there are pitfalls which are not immediately visible from the docs.
(Fractal structure) Also known as: Self-Contained Apps, Recursive Route Hierarchy, Providers, etc
Context:
I'm looking at react-redux-starter-kit and it suggests to use a fractal structure to organize the folders. If I understood well, this approach require to organize the project folders by feature and nest the route recursively.
So, if I have a "events" resources where
/events lists all the events
/events/new show a form to insert a new event
/events/1/details show the details about the event with
id 1
Starting from the boilerplate, I have to add the new route folder like:
├── src # Application source code
│ ├── main.js # Application bootstrap and rendering
│ ├── components # Reusable Presentational Components
│ ├── containers # Reusable Container Components
│ ├── layouts # Components that dictate major page structure
│ ├── static # Static assets (not imported anywhere in source code)
│ ├── styles # Application-wide styles (generally settings)
│ ├── store # Redux-specific pieces
│ └── routes # Main route definitions and async split points
│ ├── index.js # Bootstrap main application routes with store
│ ├── Root.js # Wrapper component for context-aware providers
~ ~
│ ├── Events # Fractal route
│ │ ├── index.js # Route definitions and async split points
│ │ ├── components # Presentational React Components
│ │ ├── container # Connect components to actions and store
│ │ ├── modules # Collections of reducers/constants/actions or single DUCK module
│ │ └── routes # Fractal sub-routes (** optional) <-------------
│ │ │
│ │ └── New
│ │ │ ├── index.js # Route definitions and async split points
│ │ │ ├── assets # Assets required to render components
│ │ │ ├── components # Presentational React Components
│ │ │ ├── container # Connect components to actions and store
│ │ │ ├── modules # Collections of reducers/constants/actions or single DUCK module
│ │ │ └── routes # Fractal sub-routes (** optional) <-------------
│ │ │
│ │ └── Details
│ │ ├── index.js # Route definitions and async split points
│ │ ├── assets # Assets required to render components
│ │ ├── components # Presentational React Components
│ │ ├── container # Connect components to actions and store
│ │ ├── modules # Collections of reducers/constants/actions or single DUCK module
│ │ └── routes # Fractal sub-routes (** optional) <-------------
~ ~
│ └── NotFound # Capture unknown routes in component
~
With New and Details folder nested under the root Event folder.
The docs highlight this main pros:
It scales better than a flat directory structure, with folders for
components, containers, etc.
Routes can be be bundled into "chunks"
using webpack's code splitting and merging algorithm
Since logic is self-contained, routes can easily be broken into separate
repositories and referenced with webpack's DLL plugin for flexible,
high-performance development and scalability.

The one drawback or con I've encountered with a similar structure is if/when things starts being used outside of it's hierarchy, then you have to use a lot of ../../.. in your imports.
For example, say that you get the requirement that on your StartPage route you should show the details for the most recent event.
so now it looks like:
routes
├─Events
│ ├─New
│ ├─Details
├─StartPage
├─ components // here somewhere you import ../../Events/Details
It's not the end of the world, but your nice hierarchy isn't quite as strictly hierarchical anymore.

The one drawback that I can speak to is not being able to at a glance view all available routes and their respective components, this can be mitigated by making some descriptive comments, but it does increase the complexity of your route configuration. I would also try to keep nesting folders to minimum since there is cognitive load associated with getting the levels of nesting right in your import statements i.e ../../../../../
these can get out of hand if you have many nested routes.

Related

How to exclude files when create build in react js

Currently I am working one react js project. there i create two different app in one application (eg. app1 and app2).
i have render two app base on condition i want to deploy this app one by one firstly create build with app1 and
after few time create build with app2.
so what i want to do is when i m create build for folder app1 in that time i don't want move app2 folder in build.
and also don't want to move unnecessary components with build.
so is there any way to exclude my app2 folder from build and others file ? how can i achieve that?
here is my app structure.
.
└── myApplication/
├── public
└── src/
├── app1/
│ ├── assets
│ ├── service
│ ├── hooks
│ ├── components
│ └── index.js
├── app2/
│ ├── assets
│ ├── service
│ ├── hooks
│ ├── components
│ └── index.js
├── router.js
├── App.js
├── index.js
├── app.css
├── service
└── hooks

Sucrase moves only JS files

Im using the lib sucrase to use ES6 modules in NodeJS(import/export) and i'm using the follow command to generate a build of my project in commonJ:
sucrase ./src -d ./build --transforms imports where ./src is my project folder and ./build is the output. When the process is finished i have a build folder with my javascripts files converted to commonJS but the assets files and every no-JS files are moved to this build folder, like images and css files in public directory or PUG files in views, only the JS files and directories are moved. I dont know if is possible do this with sucrase cause i've already read the doc, but maybe its possible do with another lib(sucrase with other lib).
My project structure(src)
│ app.js
│
├───public
│ │
│ ├───images
│ │ some-image.png
│ │
│ ├───javascripts
│ │ script.js
│ │
│ └───stylesheets
│ styles.min.css
│
├───routes
│ index.js
│
└───views
│ index.pug
│ layout.pug
│
└───parts
footer.pug
header.pug
My project structure after converted(build)
│ app.js
│
├───public
│ │
│ ├───images
│ │
│ ├───javascripts
│ │ script.js
│ │
│ └───stylesheets
│
├───routes
│ index.js
│
└───views
└───parts
I read sucrase document and found that ONLY support load JS files (JSX, TS, etc). If you want to load another kind of files, use file-loader.
Sucrase also offers a package called #sucrase/webpack-loader. You can give it a try, but I'm not sure it supports to load images, non-JS files and so on.
Last but not least, sucrase documentation is not great, and I highly recommend not using it because you will have trouble with it community like this. Use Webpack, Babel or parcel instead if you want to deep dive into bundler. For modern approach for example with ReactJS, I higly recommend using gatsby to forget all things about bundler, you totally can move your react app into gatsby easily.
Maybe this might not help you resolve problem, but I hope I can give you a picture on your problem. Sharing is more important thing. Good luck!

Share common dependencies between contexts

Premise
Let's say I have two different AMD-based AngularJS apps, each of them with their own sets of controllers, directives, services, etc. Each of them are bundled in their own dist/{app-name}.min.js and loaded in <script> tags in the same HTML page (this is all in the context of a CMS that then contains these apps among other things)
Now the apps end up sharing some of the services, directives, and vendor libraries like angular itself, moment, jQuery, etc, so I've made another folder for all of these resources, which results in a bundle that will be added to the page before the js bundles of the apps:
<script src="/some-path/dist/shared-resources.min.js"></script>
<script src="/some-path/dist/first-app.min.js"></script>
<script src="/some-path/dist/second-app.min.js"></script>
This is the resulting folder structure:
.
├── shared-resources/
│ ├── dist/
│ ├── src/
│ │ └── common/
│ │ ├── directives/
│ │ ├── modules/
│ │ ├── services/
│ │ └── vendor/
│ └── build.js
│
├── first-app
│ ├── dist/
│ ├── src/
│ │ ├── first-app/
│ │ │ ├── controllers/
│ │ │ ├── modules/
│ │ │ ├── services/
│ │ │ ├── directives/
│ │ │ └── app.js
│ │ └── first-app.js
│ └── build.js
│
└── second-app
├── dist/
├── src/
│ ├── second-app/
│ │ ├── controllers/
│ │ ├── modules/
│ │ ├── services/
│ │ ├── vendor/
│ │ └── app.js
│ └── second-app.js
└── build.js
This is an example of what the build.js file for the common modules looks like
({
baseUrl: 'src',
removeCombined: true,
out: 'dist/shared-resources.min.js',
paths: { // forcing a `common/{modulename}` convention
'common/jquery': 'common/vendor/jquery.min',
'common/moment': 'common/vendor/moment.min',
'common/angular': 'common/vendor/angular/angular.min',
},
shim: {
'common/angular': {
exports: 'angular',
}
},
include: [
'common/modules/vendors', // Just a bundle of every vendor modules
'common/directives/common-directive',
'common/services/common-service'
],
})
Now my intention was to have all the shared modules being namespaced with common/, so each of the apps could require common/angular, common/directives/common-directive, and so on, and then exclude the common path when creating their bundle (since all the common modules are already present in the shared-resources.js bundle), for example:
// first-app/src/first-app/controllers/app-controller.js
define([
'first-app/modules/controllers',
'first-app/services/app-service',
'common/services/common-service'
], function (controllers) {
'use strict';
controllers.controller('AppController', ['CommonService', 'AppService', function (CommonService, AppService) {
CommonService.call();
AppService.call();
}]);
});
// first-app/build.js
({
baseUrl: 'src',
out: 'dist/first-app.min.js',
paths: {
'common': 'empty:'
},
name: 'first-app',
deps: ['first-app/app']
})
Problem
The problem is how these two apps, which again are both loaded on the page (this can't be avoided), are supposed to correctly look up these common modules.
Given that each of the apps have obviously a different baseUrl, they are put in different RequireJS contexts, otherwise the baseUrl of the second app would override the baseUrl of the first one, causing the incorrect loading of its modules:
// first-app/src/first-app.js
require.config({
context: 'first-app',
baseUrl: 'first-app/src',
})(['fist-app/app']);
// first-app/src/second-app.js
require.config({
context: 'second-app',
baseUrl: 'second-app/src',
})(['second-app/app']);
But putting them in context then causes the look up for the common modules to fail, as the modules are looked in the baseUrl of the context. Actually this happens only for the second app (second in order of loading), while the first app to be included in the page can load the common modules fine
Question
So how should I make the apps to correctly share the common modules? Am I approaching this wrong? Should I use something else than RequireJS?
The context feature of RequireJS is really meant to be used to handle a case where you have to load two conflicting applications on the same page and the conflict cannot be resolved otherwise. The way you've written your code so far may have led you to want to have two baseUrl values, but there is nothing in your question that indicates that you must have two baseUrl values. There are ways to avoid it:
Modules that are part of a logical set of modules should load each other with relative paths. For instance, the module you give as example could be:
// first-app/src/first-app/controllers/app-controller.js
define([
'../modules/controllers',
'../services/app-service',
'common/services/common-service'
], function (controllers) {
paths can be set to make it look like a module is directly under baseUrl even if it is not. You could have:
paths: {
'first-app': 'first-app/src'
'second-app': 'second-app/src'
}
and yes, loading first-app/app will work. RequireJS will transform the path to first-app/src/app.js.

How to structure Redux components/containers

I’m using redux and I’m not sure about how to organize my components, I think the best is to keep them in folders with the name of the main component as the name of the folder and all inner components inside:
components
Common/ things like links, header titles, etc
Form/ buttons, inputs, etc
Player/ all small components forming the player
index.js this one is the top layout component
playBtn.js
artistName.js
songName.js
Episode/ another component
Then, in the containers folder, I’ve one container per page, that are the only ones I'm actually connecting to Redux:
containers/
HomePageApp.js
EpisodePageApp.js
...
and then the actions are one per each top component, instead of one per page, so in the page container that I connect to Redux I pass all the actions of the components used in that page. For example:
actions/
Player.js
Episode.js
...
Am I doing this right? I haven't found much information about it googling, and the ones I've found I think they are limited to small projects.
Thanks!
In the official examples we have several top-level directories:
components for “dumb” React components unaware of Redux;
containers for “smart” React components connected to Redux;
actions for all action creators, where file name corresponds to part of the app;
reducers for all reducers, where file name corresponds to state key;
store for store initialization.
This works well for small and mid-level size apps.
When you want to go more modular and group related functionality together, Ducks or other ways of grouping functionality by domain is a nice alternative way of structuring your Redux modules.
Ultimately choose whatever structure works best for you. There is no way Redux authors can know what’s convenient for you better than you do.
This is more a question about best practices / code style, and there is no clear answer. However, a very neat style was proposed in the React redux boilerplate project. It's very similar to what you currently have.
./react-redux-universal-hot-example
├── bin
├── src
│ ├── components // eg. import { InfoBar } from '../components'
│ │ ├── CounterButton
│ │ ├── GithubButton
│ │ ├── InfoBar
│ │ ├── MiniInfoBar
│ │ ├── SurveyForm
│ │ ├── WidgetForm
│ │ └── __tests__
│ ├── containers // more descriptive, used in official docs/examples...
│ │ ├── About
│ │ ├── App
│ │ ├── Home
│ │ ├── Login
│ │ ├── LoginSuccess
│ │ ├── NotFound
│ │ ├── RequireLogin
│ │ ├── Survey
│ │ ├── Widgets
│ │ └── __tests__
│ │ └── routes.js // routes defined in root
│ ├── redux
│ │ ├── init.js
│ │ ├── middleware
│ │ │ └── clientMiddleware.js // etc
│ │ └── modules // (action/creator/reducer/selector bundles)
│ │ ├── auth.js
│ │ ├── counter.js
│ │ ├── reducer.js
│ │ ├── info.js
│ │ └── widgets.js
│ ├── server
│ │ ├── middleware
│ │ └── actions // proxy to separate REST api...
│ └── utils
│ │ ├── validationUtility.js // utility only (component-level definitions inside respective dir)
│ └── createDevToolsWindow.js // etc
├── static
│ ├── dist
│ └── images
└── webpack
I prefer keeping smart and dumb components in the same file, but use default export for smart component and export for presentation/dumb components. This way you can reduce file noise in your directory structure. Also group your components by "View" i.e. (Administration => [admin.js, adminFoo.js, adminBar.js], Inventory => [inventory.js, inventoryFoo.js, inventoryBar.js], etc).
I have no strong opinion about the component directories, but I like putting the actions, constants and reducers together:
state/
actions/
index.js
...
constants.js
reducers.js
I alias state with with webpack so in the container components I can import {someActionCreator} from 'state/actions';.
This way, all the stateful code in the app resides in a single place.
Note that reducers.js could be split into multiple files simply by making a reducers/ directory like actions/ and you wouldn't have to change any import statements.
In Redux you have one entry point for your actions (actions/ folder) and an entry point for the reducers (reducers/ folder).
If you go with domain-based code structure you simplify domain/feature implementation and maintenance... on the other hand you are complicating component dependencies and app state/logic maintenance.
Where are you gonna put reusable components? inside feature/domain folder? so if you need a reusable component from other feature/domain you need to create a dependency between domains? mmh not so good for large app!
When you have to combine reducers, domain-code-structure takes away what it gave you previously.
You are only creating single redux modules for each domain/feature.
domain-code-structure should be good in some/most cases, but this is not Redux.
I have a boilerplate with react, redux folder structure and it's being used for many company projects. You can check it out here: https://github.com/nlt2390/le-react-redux-duck

Where should I put AngularJS Factories & Services?

I'm working to cleanly structure my AngularJS app according to best practices, which includes separating the controllers and app into different script files.
Quick question: where should I put my factories and services? I am asking in the context of having factories & services that will be accessed outside of the scope of a single controller as well as having some that are within the scope of a single controller.
Update: the immediate answer below is no longer correct. Please see the latest addendum (written March 1, 2015) to this answer.
Got it! According to Brian Ford's article on Building Huuuuuuuge Angular Apps, the best practice appears to be to connect services and factories to the app in a separate file, like so:
root-app-folder
├── index.html
├── scripts
│ ├── controllers
│ │ └── main.js
│ │ └── ...
│ ├── directives
│ │ └── myDirective.js
│ │ └── ...
│ ├── filters
│ │ └── myFilter.js
│ │ └── ...
│ ├── services
│ │ └── myService.js
│ │ └── ...
│ ├── vendor
│ │ ├── angular.js
│ │ ├── angular.min.js
│ │ ├── es5-shim.min.js
│ │ └── json3.min.js
│ └── app.js
├── styles
│ └── ...
└── views
├── main.html
└── ...
(PSST! In case you're wondering, Brian Ford is part of the AngularJS team, so his answer seems pretty legit.)
Addition (April 24, 2013)
This just in: Yeoman is a fantastic tool for generating apps with the proper directory structure for big, functional Angular apps. It even has Grunt & Bower packed in!
Addendum (March 1, 2015)
According to a comment via PaoloCargnin, Google actually recommends a different structure, as detailed by this document. The structure should look like this:
sampleapp/
app.css
app.js //top-level configuration, route def’ns for the app
app-controller.js
app-controller_test.js
components/
adminlogin/
adminlogin.css //styles only used by this component
adminlogin.js //optional file for module definition
adminlogin-directive.js
adminlogin-directive_test.js
private-export-filter/
private-export-filter.js
private-export-filter_test.js
userlogin/
somefilter.js
somefilter_test.js
userlogin.js
userlogin.css
userlogin.html
userlogin-directive.js
userlogin-directive_test.js
userlogin-service.js
userlogin-service_test.js
index.html
subsection1/
subsection1.js
subsection1-controller.js
subsection1-controller_test.js
subsection1_test.js
subsection1-1/
subsection1-1.css
subsection1-1.html
subsection1-1.js
subsection1-1-controller.js
subsection1-1-controller_test.js
subsection1-2/
subsection2/
subsection2.css
subsection2.html
subsection2.js
subsection2-controller.js
subsection2-controller_test.js
subsection3/
subsection3-1/
etc...

Categories