facebook / create-react-app

Set up a modern web app by running one command.
https://create-react-app.dev
MIT License
102.82k stars 26.88k forks source link

Support absolute paths with packages/ folder #741

Closed gaearon closed 8 years ago

gaearon commented 8 years ago

We’ve been using Lerna in Create React App, and I really like its approach. I think we should support a similar workflow (even without Lerna itself) for the “absolute paths” feature.

I imagine it working like this:

package.json
public/
src/
  index.js # can import app/banana.js or harry-potter/wand.js
packages/
  app/
    banana.js # can import harry-potter/wand.js
  harry-potter/
    wand.js # can import app/banana.js
node_modules/
  app -> packages/app
  harry-potter -> packages/harry-potter
  other-deps

Am I missing why this would be a bad idea?

bernatfortet commented 7 years ago

Does the NODE_PATH=src/ solution work for react-native?

j-hannes commented 7 years ago

Adding NODE_PATH=src/ in .env works great! Thanks!

Just to add on this: In case you run your own ESLint config on top you can to add

  settings: {
    'import/resolver': {
      node: {
        paths: [path.resolve(__dirname, './src')],
      },
    },
  },

to your .eslintrc so absolute paths get resolved correctly by linter plugins.

woniesong92 commented 7 years ago

@will-stone's solution of adding NODE_PATH=src/ to .env works for import {something} statements, but not for @import {something}.scss statements for SASS.

kellyrmilligan commented 7 years ago

You can use the --include-path option in the cli for that

hmaidasani commented 7 years ago

For some reason, my .env file with NODE_PATH=src/ is ignored. When I run npm start, I get module not found errors to my components. But if I run NODE_PATH=src/ && npm start, I get it working. Any ideas?

treio commented 7 years ago

Yeah, I noticed this too. I solved it with cross-env:

npm install --save cross-env

Then in package.json:

...
"scripts": {
    "start": "cross-env NODE_PATH=src/ react-scripts start",
...

Hope this helps.

gaearon commented 7 years ago

It’s a bug in 1.0.0. See https://github.com/facebookincubator/create-react-app/issues/2225 for updates.

Satyam commented 7 years ago

I am somewhat late to this discussion but I'd like to support @FFX01 suggestion, which is, more or less, what I use. Please stop forcing developers to an arbitrarý folder structure. Placing things into src/node-modules or any other place, for that matter, is not a good idea.

I currently use WebPack's resolve.alias to create virtual folders for components, store, utils, jest utils. I have adopted a leading underscore instead of @FFX01 's at-sign, but any symbol would do, whatever makes sure that your virtual folders would never collide with an existing package. By using resolve.alias

This is a sample of imports I have in my code:

import isPlainClick from '_utils/isPlainClick';
import { selSector, selUsername } from '_store/selectors';
import { logout, ensureUser } from '_store/actions';
import Menu from '_components/menu';
import Login from '_components/login';

Where is _utils actually located? Actually, you don't need to know. Nor are you forced to keep it in a fixed position under src, as it would happen if the only absolute directory reference is src. Nor are you forced to tinker with other tools that regularly ignore node_modules.

Besides, using resolve.alias instead of the Webpack's DefinePlugin you restrict the use to a particular problem (which is the one we want solved) and prevent abuse.

For example, just like we have a .env file we could have a .alias file containing a simple object literal. This is why I used an underscore instead of any other symbol, as it still makes for a valid JS identifier and spares us from quoting the property names:

{
  _utils: './utils',
  _components: './components',
  // ....
}

All relative folder references would be solved relative src/ which should be recommended (and not forced, please).

Thanks and sorry for my lateness.

cecilemuller commented 7 years ago

The reason I personally don't rely on Webpack resolve.alias for that is that only Webpack would know where the modules are, whereas other tools (like tests, coverage, analysis, documentation, or the code editor Intellisense) wouldn't automatically know where they are.

Satyam commented 7 years ago

@cecilemuller In testing, I use jest.mock to replace dependencies, for example:

jest.mock('_store/selectors.js', () => ({
  configSelectors: {
    get: jest.fn((state, name) => state.config[name]),
  },
}));

In this case I am mocking an import with a mock function, but with an .alias file it would be possible to automate the creation of a moduleNameMapper (http://facebook.github.io/jest/docs/en/configuration.html#modulenamemapper-object-string-string) configuration for jest tests.

I have no solution for editors, except praying for a plugin, eventually.

tugorez commented 7 years ago

I'm using babel-plugin-module-resolver in a RN project and it works well with absolute paths. Also it requires just minimum configuration. If i'm not wrong all it does is replacing the absolute paths for theirs relative counterparts in compile time. I'm not sure if it's considered "magic" or if it breaks the node resolution mechanism (I'm new in this) but I hope it is helpful.

Sorry for my bad english.

gaearon commented 7 years ago

We’re going to keep supporting NODE_PATH=src as described in earlier comments. This is enough to keep this issue at bay for now. We might revisit this later.

mizx commented 7 years ago

For those using react-scripts-ts:

  1. add NODE_PATH=src/ to the .env file (thanks will-stone!)
  2. add "baseUrl": "./src" to your tsconfig.json file.
jasan-s commented 7 years ago

Using the above accepted solution I still can't achieve the following: import { componentA, componentB } from 'components' instead I still have to do the following: import componentA from 'components/componentA/componentA' import componentB from 'components/componentB/componentB'

My component structure is the following:

 src
  components
     componentA  
       componentA.js
     componentB  
       componentB.js
     index.js
will-stone commented 7 years ago

@jasan-s as support questions probably belong on StackOverflow, can you paste your question there and include the contents of your index.js file?

leob commented 7 years ago

@jasan-s I think that should work if the contents of your 'components/index' is correct ...

Haven't tried it out with import, only with require, but if your index.js successfully imports the components from the subpaths and then exports them again using a "{}" then I don't see why it wouldn't work?

Maybe it's the difference between import and require ...

jasan-s commented 7 years ago

@leob I get an error using it with import.
index.js file looks as follows:

export  componentA from './componentA/componentA 
export  componentB from './componentB/componentB 

I'll try stack overflow, see if i can get some help there as well.

will-stone commented 7 years ago

@jasan-s try

export  {default as componentA} from './componentA/componentA'
export  {default as componentB} from './componentB/componentB'
jasan-s commented 7 years ago

that works :) thanks @will-stone

leob commented 7 years ago

Wow, {default as ...} syntax that's new for me ... like I said I tried it with require only.

Ehesp commented 7 years ago

Don't know about you guys but I much prefer using babel-plugin-root-import:

// .babelrc

{
  "presets": [
    "react-app"
  ],
  "plugins": [
    "babel-plugin-transform-es2015-modules-commonjs",
    ["babel-plugin-root-import", {
      "rootPathSuffix": "src/"
    }]
  ]
}

In any component you can import like so:

// From
import someFile from '../../../../utils/someFile';

// To
import someFile from '~/utils/someFIle';

The ~ acts as your project src/ path. Much cleaner imo, not had any issues with it.

will-stone commented 7 years ago

@Ehesp I didn't think it was possible to use babel plugins with an un-ejected CRA, if that's the case then this wouldn't be a solution users could add, but maybe something that could be considered by the CRA team to be put directly in to react-scripts? Is it cross platform compatible?

Ehesp commented 7 years ago

@will-stone Ah yes sorry this is an ejected app!

will-stone commented 7 years ago

Having checked out VueJS for a while, I'm all in favour of using the "@" prefix to denote the src dir.

alexeyraspopov commented 7 years ago

I was able to use paths from src folder without dots by adding

NODE_PATH = node_modules:src

to .env file. Cheap and fast solution.

will-stone commented 7 years ago

@alexeyraspopov is there technically any benefit to using

NODE_ENV = node_modules:src

over

NODE_PATH=src/

?

alexeyraspopov commented 7 years ago

My bad, I apologize, it should be NODE_PATH of course. Edited my comment above.

pedro-mass commented 7 years ago

I think the question still remains: do we need to add node_modules to the path, or is this done regardless and we can just specify src?

alexeyraspopov commented 7 years ago

A quick test shows that you can just set NODE_PATH = src.

pedro-mass commented 7 years ago

Was just about to comment that lol. It does break VSCode's cmd+click feature, but I'm fine with that for shorter imports.

Vanuan commented 6 years ago

The issue with symlinking node_modules is that babel will skip transpiling these files by default.

Satyam commented 6 years ago

@Vanuan One alternative is to create a folder, say src/vRoots and put all the symlinks into that folder. Since it is not under node_modules Babel will compile its contents. Then you add that folder to NODE_PATH.

As a convention, I started all the symlinks with an underscore, which is otherwise forbidden in NPM so there would be no conflict with npm packages, and it made it clear to the developer that those were virtual folders, not dependencies that could be found in node_modules.

The advantage was that it was meant to test several architectures so I could swap various alternatives by changing the folders the symlinks pointed to, for example, my _connector symlink could point to a regular HTTP client, a WebSockets one or even Firebase. Likewise, on the server side, my _db symlink could point to MySql or MongoDb

Since this folder with the symlinks is along the other source files, exclude it from linting, testing or any other utilities that my browse the folders, otherwise you might get in a loop.

alexdevero commented 6 years ago

I still can't make this work properly. I have in NODE_PATH=src/ in .env. However, some imports still fail.

This fails:

// Home.jsx
import { Link } from './Links'

This works:

// Home.jsx
import { Link } from './../atoms/Links'

Folder structure:

src/
    app/
        atoms/
            Links.jsx
        pages/
            Home.jsx
        main.jsx
    assets/
jasan-s commented 6 years ago

@alexdevero setting NODE_PATH=src/ would like nable you to import from absolute base path like so in home.jsx. import Link from 'app/atoms/Links'

jasan-s commented 6 years ago

@alexdevero setting NODE_PATH=src/ would enable you to import from absolute base path like so in home.jsx. import Link from 'app/atoms/Links'

alexdevero commented 6 years ago

That worked. Thank you very much @jasan-s.

Is it somehow possible, without ejecting, to enable import { Link } from './Links' or import { Link } from 'Links'?