FormidableLabs / builder

An npm-based task runner
https://github.com/FormidableLabs/builder
MIT License
319 stars 26 forks source link

Alternatives to "the module pattern". #153

Open trusktr opened 6 years ago

trusktr commented 6 years ago

I wonder if there's alternatives to the module.exports = require trick. At first sight, it's strange and not clear what that does, as no one ever does that sort of thing.

Are there any alternatives that may have even been considered (even if they didn't work)?

ryan-roemer commented 6 years ago

We spent a lot of time wrangling with all of the different things that npm tree flattening can do with having separated prod and dev archetypes -- prod archetypes having dev dependencies in dev archetypes is the real problem.

This bug shows pretty concisely what we were originally up against (and also include Joel coming up with the brilliant module pattern):

We spent a good amount of time trying to discuss how node require resolution works, what these complexities mean for builder, and why we think the module pattern is the best solution at https://github.com/FormidableLabs/builder#node-require-resolution-and-module-pattern

All that said, I'm totally open for (1) updated documentation if README section isn't clear enough, or (2) a different technical solution that can handle correctly resolving dependencies from dev archetypes through tree flattening in either npm or yarn.

trusktr commented 6 years ago

Ah, I see, the "module pattern" is specifically really useful if archetype needs to import a dep from archetype-dev.

In my case I'm not using an archetype-dev, and what I found is that for application dependencies, we have a couple more options without having to introduce a plugin, just using plain Webpack config:

1) aliases

We can write stuff like follows for resolve.alias:

        alias: {
            'lodash': path.join(path.dirname(require.resolve('<ARCHETYPE>')), 'dependencies', 'lodash'),
        },

where files in <ARCHETYPE>/dependencies contain something like

// <ARCHETYPE>/dependencies/lodash.js
module.exports = require('lodash')

2) module resolution

Another thing that works in the Webpack case is just adding the archetype to resolve.modules:

// <ARCHETYPE>/config/webpack.config.js

const alsoResolveRelativeToArchetype = () => [
    // when the ARCHETYPE is `npm link`ed, or in older versions of NPM, loaders will be found in the
    // ARCHETYPE's node_modules.
    path.relative(CWD, path.join(path.dirname(require.resolve('builder-js-package')), 'node_modules')),

    // otherwise, loaders can also be found in the app's node_modules when
    // deps are flattened (f.e. when the ARCHETYPE is not `npm link`ed).
    'node_modules',
]

// ...

    resolve: {
        modules: alsoResolveRelativeToArchetype(),
    },
    resolveLoader: {
        modules: alsoResolveRelativeToArchetype(),
    },

That will cause dependencies of the ARCHETYPE to have precedence over app dependencies, so lodash can be found there first if it isn't installed by the app.


Sidenote, despite not having a dev archetype, I see these (seemingly harmless) errors in the console.