webpackwebpack-4

Webpack 4x how to exclude multiple node_modules directories


I have a project with multiple node_modules directories

myproj
   |-->app1
     package.json
     |-->node_modules
     |-->src

   |-->origapp_reactnative
     package.json
     |-->node_modules
     |-->shared_src

   |-->app2
     package.json
     |-->node_modules
     |-->src

When bulding app1 or app2 with webpack (from their respecitive root dirs.). I must specify

   resolve.modules=[path.resolve(__dirname,"./node_modules")]

If I do not do that, then webpack will try to pull code from

   |-->origapp_reactnative
     |-->node_modules

Because app1 or app2 include sources from shared_src. And webpack tries to follow nodejs convention and look for node_modules in the dir next to shared_src.

So that's why I am setting resolve.modules to an absolute path.

However, that creates another problem, that I cannot overcome: webpack flattens the dependency tree within node_modules specified by absolute path. And that creates dependency problem, where modules of different versions cannot coexist.

So I am looking for a way to use, therefore, relative path

   resolve.modules=["./node_modules"]

But need help to figure out how to exclude the node_modules

   |-->origapp_reactnative
     |-->node_modules

from webpacks consideration.

( I have tried to instruct babel loader as discussed here [1], not to look there -- but that's not enough, because compilation still will fails. )

[1] Webpack 2: How to exclude all node_modules except for


Solution

  • You seem to be almost there. When you build app1 and only want to use node_modules from app1, you can use resolve.modules with a relative path app1/node_modules.

    Solution with resolve.modules

    webpack config in app1:

    const path = require("path");
    ...
    
    module.exports = {
      ...
      resolve: {
        extensions: [".jsx", ".js"],
        modules: [path.basename(__dirname) + "/node_modules"]
      },
    };
    

    (Assumptions: cwd pointed to app1's root, webpack config in app1's root)

    With path.basename, you can make your config more independent from the actual project name, effectively having the setting modules: ["app1/node_modules"]. If you import something from shared_src, which on his part uses node_modules, it will traverse via node resolution up to myproj and from there find the path app1/node_modules.

    You could also add node_modules as fallback, if you have additional packages only installed in the shared project:

    // first entry takes precedence.
    modules: [path.basename(__dirname) + "/node_modules", "node_modules"]
    

    resolveLoader can be set as extra property for loaders, if you happen to get an error not being able to find loaders from node_modules afterwards.

    Alternative: resolve.alias

    If all you need is importing a few packages from a specific node_modules folder, you could also use resolve.alias (for example resolve react here from ./node_modules):

    resolve: {
      alias: {
        react: path.resolve("./node_modules/react")
      }
    }
    

    Note about babel-loader

    ( I have tried to instruct babel loader as discussed here 1, not to look there -- but that's not enough, because compilation still will fails. )

    Babel-loader and Co. use the absolute file path that is already resolved for rule conditions, if you want to exclude something to be transformed. In every case exclude and other filter options of loaders do not prevent your imported modules (like the ones in node_modules) from being excluded. When modules are filtered out, it only means, they aren't further processed by Babel loader (or other loaders, where you have defined rule conditions).

    Hope, that helps.