Skip to content
This repository has been archived by the owner on Jun 3, 2019. It is now read-only.

Access components from common root #336

Closed
bryaan opened this issue Jan 20, 2017 · 23 comments
Closed

Access components from common root #336

bryaan opened this issue Jan 20, 2017 · 23 comments
Labels

Comments

@bryaan
Copy link

bryaan commented Jan 20, 2017

Directory Structure:

DemoApp
/components
  /Button
    /index.js
/About
  /index.js

In my About/index.js file I want to do:

import Button from 'components/Button' 

instead of:

import Button from '../components/Button' 

Which can quickly become a problem for large apps requiring multiple walks back up the directory tree.

How can I set this up?

@ctrlplusb
Copy link
Owner

Hey @BryanARivera

There have been a few discussions on this.

See #318 and #203 ✌️

@bryaan
Copy link
Author

bryaan commented Jan 20, 2017

@ctrlplusb I don't see any ref to it in the FAQ as you had suggested. Is there any concrete example or just these clues?

@bryaan
Copy link
Author

bryaan commented Jan 20, 2017

If I want to use @bradennapier's solution, which file should that snippet go in?

@bryaan
Copy link
Author

bryaan commented Jan 20, 2017

@bradennapier You posted that you had IDE tooling to inject relative paths.

Do you know of any such plugin for Atom?

@bradennapier
Copy link

bradennapier commented Jan 20, 2017

Hey Bryan,
What I showed was using aliases. Here is my resolve from configFactory.js

resolve: merge(
      // These extensions are tried when resolving a file.
      {
        extensions: config.bundleSrcTypes.map(ext => `.${ext}`)
      },
      // Add to resolve modules to reduce ../ overhead
      {
        modules: [ './shared', path.resolve(appRootDir.get(), 'src/shared'), 'node_modules' ],
        plugins: [ 
          componentResolver('existing-directory', 'undescribed-raw-file')
        ]
      },
      // Within aliases we allow specifying ~path to make it relative
      // to the appRootDir.  This way we don't need to have 'path' within
      // the config file.
      ifElse(config.webpack_aliases)(() => ({
        alias: Object.keys(config.webpack_aliases).reduce((p, c) => {
          const a = config.webpack_aliases[c]
          if ( a.startsWith('~') && ! a.startsWith('~/') ) {
            p[c] = path.resolve(appRootDir.get(), a.replace('~', ''))
          } else { p[c] = a }
          return p
        }, {})
      }))
    ),

I am actually not using the aliases anymore as this method of resolving seems to work perfectly for me. I still have the above available in my configFactory so that I can add aliases anytime I want.

This method allows me to include "shared" components at different hierarchy levels which is pretty awesome. For example, given this directory structure:

image

Within ProjectDashboard or ProjectSelect I can do

import MyComponent from 'components/MyComponent'

whereas ALL screens can access

import Button from 'components/Button/Modern'

obviously you have to make sure you don't create clashes which shouldn't generally be an issue since "shared" components should be used as little as possible.

Note: I added the component resolver as well. This essentially allows me to make it resolve {DIR}/{DIR}.js so if I have components/MyComponent/MyComponent.js I can just do import MyComponent from 'components/MyComponent - this is important for me as if I have a bunch of tabs open that say "index.js" I will probably be super confused. What is nice is that if it cant find /${DIR}/${DIR} it will still look for /${DIR}/index.js so it is an easy change. :-P

@bradennapier
Copy link

bradennapier commented Jan 20, 2017

I do not need or inject any kind of relative paths. My setup as described appears to do the job as needed.

@bryaan
Copy link
Author

bryaan commented Jan 20, 2017

I will have to give consideration to your approach. Thanks for all the info.
I have been using the fractal directory structure described here.
Do you see any reasons why one approach or the other may be superior?

Is there any simple edit to what you just posted that would allow this work in any folder?

I assume a 'dirty' hack would be to place the entire project in /app/shared. Is that correct?

@bradennapier
Copy link

bradennapier commented Jan 20, 2017

This sets the entire root "shared" directory as a relative path as well as the trickle-down effect. So for example, I can do things like import sagas from 'sagas'

You do have to be careful though because it can definitely cause conflicts. For example if I had a redux/index in the project then any redux imports would be problematic as it would override them. I haven't converted yet but my plan is to use capitalized directories locally (Redux, Sagas, Types, Reducers) which should avoid most clashes with node_modules

@bryaan
Copy link
Author

bryaan commented Jan 20, 2017

I see. That means both approaches work together. Will implement your code. Thanks!

@smirea
Copy link

smirea commented Jan 25, 2017

@bradennapier how did you make your resolve modules work with server side rendering? I get "cannot find module" errors if I try to use that approach...

@bradennapier
Copy link

@smirea I believe these are the key pieces to the puzzle

{
        modules: [ './shared', path.resolve(appRootDir.get(), 'src/shared'), 'node_modules' ],
        plugins: [ 
          componentResolver('existing-directory', 'undescribed-raw-file')
        ]
      },

I had the same issue originally. The key is using the relative shared and the absolute shared. Once I had both in there it works across the board both server and client and has been excellent.

@smirea
Copy link

smirea commented Jan 25, 2017

is the componentResolver necessary? I have

    resolve: merge(
      // These extensions are tried when resolving a file.
      {
        extensions: config.bundleSrcTypes.map(ext => `.${ext}`)
      },
      // Add to resolve modules to reduce ../ overhead
      {
        modules: [ './shared', path.resolve(appRootDir.get(), 'src/shared'), 'node_modules' ],
      },
    ),

and it still fails.

EDIT: If so, how do you have it configured?

@bradennapier
Copy link

I don't think it should be, that is for a different purpose, although it's a very nice plugin. Have you restarted your entire process?

@smirea
Copy link

smirea commented Jan 25, 2017

yep, i'm doing rm -rf .happypack; rm -rf build; npm run development just to be sure. Same thing happens if I build and try to run the production build.

Everything works fine if I disable server side rendering, so this is exclusively an issue within the node env.

You sure you're also not setting custom NODE_PATH=./src/shared or something somewhere? That's how you used to hack it for node before.

Also, how is your componentResolver configured?

@bradennapier
Copy link

bradennapier commented Jan 25, 2017

Its just https://github.com/mgcrea/component-webpack-resolver-plugin which allows me to have a better organizational structure.

Are you including "shared" when you try to import? Odd that it is working here. Maybe its something new in Node? I'm on 7.4

/shared
-- /components
---- /MyComponent
------ /MyComponent.js
import MyComponent from 'components/MyComponent'

@bradennapier
Copy link

I have made quite a few modifications to how things work so i'm looking to see if I added something else to make it work, but I'm pretty sure that was all I did on that regard.

@bradennapier
Copy link

resolve: {
      // These extensions are tried when resolving a file.
      extensions: config.bundleSrcTypes.map(ext => `.${ext}`), 
      
      // Allow recursive searching through ./shared folders in our app 
      // to reduce ../ overhead required.
      modules: [ './shared', path.resolve(appRootDir.get(), 'src/shared'), 'node_modules' ],
      
      // Allow us to import a folder and have it look for the matching filename
      // for example
      // import Component from 'shared/components/MyComponent' would find
      // shared/components/MyComponent/MyComponent.js
      plugins: [ 
        componentResolver('existing-directory', 'undescribed-raw-file')
      ],
      
      alias: config.webpack_aliases &&
        Object.keys(config.webpack_aliases).reduce((p, c) => {
          const a = config.webpack_aliases[c]
          if ( a.startsWith('~') && ! a.startsWith('~/') ) {
            p[c] = path.resolve(appRootDir.get(), a.replace('~', ''))
          } else { p[c] = a }
          return p
        }, {
        // Merging any defined aliases in config with the items
        // below.
        // This is required for the modernizr-loader
        // @see https://github.com/peerigon/modernizr-loader
        modernizr$: path.resolve(appRootDir.get(), './.modernizrrc')
      })
    },

@bradennapier
Copy link

bradennapier commented Jan 26, 2017

Ahh I think I know the issue, you can not use the relative paths until you are in the es6 environment. I don't use the resolution technique anywhere but in the main shared folder.

@ctrlplusb
Copy link
Owner

Closing this for now.

@oyeanuj
Copy link
Contributor

oyeanuj commented Oct 22, 2017

@bradennapier @smirea @bryaan Where did you folks end up with this? What all did you folks end up modifying or adding to make this work?

@SleepWalker
Copy link

I've changed config/values.js like so:

import appRootDir from 'app-root-dir';

const values = {
    // ...
    webpackConfig: (webpackConfig, buildOptions) => {
      // eslint-disable-next-line no-unused-vars
      const { target, mode } = buildOptions;

      // Example:
      /*
      if (target === 'server' && mode === 'development') {
        webpackConfig.plugins.push(new MyCoolWebpackPlugin());
      }
      */

      // Debugging/Logging Example:
      /*
      if (target === 'server') {
        console.log(JSON.stringify(webpackConfig, null, 4));
      }
      */

      webpackConfig.resolve.modules = ['node_modules', appRootDir.get()];

      return webpackConfig;
    },
};

Then for other cli things I've used NODE_PATH env variable, e.g.:

"scripts": {
   // ...
    "test": "cross-env NODE_PATH=./ jest"
}

After this changes applied you can do something like (and yes, it would work everywhere, even outside webpack, if you won't forget to update all the cli commands):

import config from 'config';
import { Button } from 'shared/components/ui';

// ...

In my project I've renamed shared to app, which makes the imports to look nicer.

Btw., probably it would be better to read the NODE_PATH from env and use it in webpackConfig to avoid duplication

@SleepWalker
Copy link

actually, if we will read NODE_PATH it will be possible to move that webpack config chunk in configFactory which will allow the support of absolute module pathes for all the users, that want to have such feature in their project. Simply add NODE_PATH and done. This may be done using .env files too if I'm not mistaken.

@ctrlplusb what do you think about this?

@SleepWalker
Copy link

SleepWalker commented Oct 24, 2017 via email

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

6 participants