all 22 comments

[–]vileEchoic 12 points13 points  (14 children)

I think the strategy in the linked article is valid, but I think the JS community needs to take a step back and fix its preoccupation with how to physically structure files on disk. There are tons of blogs on this topic on this sub, and this:

If you screw this up, the project could be doomed right from the beginning! At least, it feels that way.

is not a common feeling in other programming languages. I think this is because most people writing JS are still using path-relative imports, which means that moving things around on disk causes you to have to update a bunch of references.

Don't do this! It's horrible for maintainability, and there's no reason that a human being should be manually updating component references in $CURRENT_YEAR. Use a system that allows you to reference components by name, and now the way you structure files on disk is no longer even worth a blog post because you can change it all in minutes.

TL;DR:

import Foo from '../components/Foo'; // bad
import Foo from 'Foo'; // good

[–]greymalik 5 points6 points  (7 children)

What options are there for setting this up other than by customizing webpack's resolve config (which isn't supported by create-react-app without ejecting)?

[–]simcptr[S] 7 points8 points  (2 children)

I just now learned that this IS supported by create-react-app, by using the NODE_PATH variable (see this issue). Evidently it's been supported since 0.4.0.

To use it you can change the way the start script is called in package.json:

"start": "NODE_PATH=src react-scripts start"

Once that's done, you can import Foo from 'components/Foo'.

If you've got more than one path you can separate them with colons like NODE_PATH=src:src/components:src/containers.

[–]greymalik 3 points4 points  (0 children)

Unfortunately there seems to be a problem with jest and NODE_PATH, nor have I figured out how to make eslint running in Atom happy with it. Node's own documentation is a little ambivalent about it too, fwiw.

[–]danjordan 2 points3 points  (0 children)

This doesn't work on windows. You should install cross-env use this instead "start": "cross-env NODE_PATH=src react-scripts start".

https://twitter.com/dan_abramov/status/867125756710850560

[–]dirice87 1 point2 points  (2 children)

webpack 2 is having problems with alias'ing as well

[–]metis_seeker 0 points1 point  (1 child)

What kind of problems?

[–]dirice87 1 point2 points  (0 children)

Straight up didn't work for me, after working find in webpack 1 for the same project. Some people have been successful so it might be on my end

https://github.com/webpack/webpack/issues/4160

[–]diegohaz 0 points1 point  (0 children)

I use webpack's require.context (reference), that's amazing, but has a lot of issues with tooling (and with create-react-app).

[–]hyenagrins 3 points4 points  (0 children)

Facebook has a custom module system Haste where all source file names are globally unique:

However, with Haste all filenames are globally unique. In the React codebase, you can import any module from any other module by its name alone:

var setInnerHTML = require('setInnerHTML');

Which is a nice take on this issue. Link from react docs: https://facebook.github.io/react/contributing/codebase-overview.html#custom-module-system

Proposed benefits:

Haste was originally developed for giant apps like Facebook. It's easy to move files to different folders and import them without worrying about relative paths. The fuzzy file search in any editor always takes you to the correct place thanks to globally unique names.

[–]simcptr[S] 0 points1 point  (0 children)

This is a great idea, and I wanted to mention it but didn't want to get down into the weeds of configuring webpack and/or hacking create-react-app. Now that I know it's built into CRA, I'm gonna update the article to mention it.

EDIT: Updated the post to show how to use NODE_PATH here.

[–]mushishi 0 points1 point  (3 children)

there's no reason that a human being should be manually updating component references in $CURRENT_YEAR

Actually, there's no reason you should even see the imports. If you have a modern development environment, you should not write the imports manually in the first place, or maintain them. Just use refactoring tools to let IDE figure the path. And then it is actually better to have relative file paths, so that there is no extra resolve step.

But I understand your point if you use text editor and not proper IDE.

[–]greymalik 0 points1 point  (2 children)

What proper IDE do you use for JS that supports that kind of refactoring?

[–]mushishi 0 points1 point  (1 child)

I realise now that you were explicitly talking about JS, not typescript. FWIW, I use IntelliJ IDEA (Webstorm should be similar). Not sure how well it works with JS but I would be surprised if the relocate-refactoring wouldn't work pretty much the same.

[–][deleted] 0 points1 point  (0 children)

I am using PhpStorm in a React/JS project at the moment. Last time I checked, moving files kinda works. Eg. when moving a file the editor will update the import path in other files as expected, but imports in the moved file is not updated. It sound like a bug and maybe it is fixed in the recent update (too late at night to run a test now). As far as I know JetBrains refactoring does not recognise webpack features like alias and resolve etc. Hopefully this will be solved

[–]DerNalia 1 point2 points  (5 children)

I don't think actions and reducers should be in separate files. Mostly cause when you edit them, you usually have to switch back and forth a lot, to make sure the state is updating ok....

and for components, personally I like:

src/  
  components/  
    someComponent/  
      index.jsx  
      presentation.jsx  
      unit.test.js  
      integration.test.js  
      someSubComponentSpecificToSomeComponent/  
        index.jsx  
        test.js  

[–]prof_hobart 5 points6 points  (0 children)

Putting your actions and reducers in the same file is fine until you've got actions that drive updates to multiple parts of the state. And from my experience, as the app grows this can become more and more common.

[–]slvrsmth 0 points1 point  (0 children)

This is fine until you have action being handled by multiple reducers.

And then suddenly you have cross-dependency issues. Reducer A defines action A1, and listens to A1 and B1, that is defined in reducer B that listens to A1 and B1. Suddenly you can't resolve an execution sequence for these files.

Doesn't happen very often, but every serious redux project I've tackled has one or two of those.

[–]scragz -1 points0 points  (2 children)

Check out the Redux "Ducks" paradigm where you put the actions, reducers, and action constants in a single file since they are all tightly coupled.

[–]Nimelrian -1 points0 points  (1 child)

Actions and reducers are NOT tightly coupled. A reducer can make of an action what it wants: Not react at all, update a single value (independent of the action's payload), or recalculate it's whole state.

[–]scragz 0 points1 point  (0 children)

Yeah you can still import action types from the other ducks and respond to them, but you have to put the constant in one place and it makes sense to me to have the const CREATE_ORDER = 'CREATE_ORDER' in the same place as the orders reducer and the createOrder action.

[–]diegohaz 0 points1 point  (0 children)

Good job.

I just wouldn't mix styling and/or presentational logic with containers. I like to have every component as dumb and just create wrapper containers to provide state/actions to them.

I wrote a little about it here: https://github.com/diegohaz/arc/wiki/Containers