builder icon indicating copy to clipboard operation
builder copied to clipboard

Alternatives to "the module pattern".

Open trusktr opened this issue 6 years ago • 2 comments

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)?

trusktr avatar Feb 12 '18 16:02 trusktr

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):

  • https://github.com/FormidableLabs/builder/issues/107
  • (Also see) https://github.com/FormidableLabs/builder/issues/83

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.

ryan-roemer avatar Feb 12 '18 17:02 ryan-roemer

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.

trusktr avatar Feb 12 '18 19:02 trusktr