Patrik Holčák
Patrik Holčák

Reputation: 146

Webpack require expression external

I have an expression require which should get resolved in runtime but I can’t get my head around the webpack config for this simple example:

import something from 'module';
import pkg from './package.json';
let a;

if (pkg.main) {
   a = require(pkg.main);
}

The resulting build should contain the module but also require ./package.json and pkg.main in runtime as commonjs modules — in other words, exclude them from the build.

My webpack.config.js so far:

var webpack = require('webpack');

module.exports = {
  entry: './src/main.js',
  output: {
    filename: '[name].js',
    path: './build'
  },
  target: 'node-webkit',
  plugins: [
    new webpack.ExternalsPlugin('commonjs', './package.json')
  ],
  module: {
    noParse: /\.min\.js/,
    exprContextRegExp: /$^/,
    exprContextCritical: false,
    loaders: [
      {
        test: /\.js$/,
        loader: 'babel',
        exclude: /node_modules/
      }
    ]
  }
};

What happens now is the require for pkg.main results in webpackMissingModule exception and if I remove exprContextRegExp, the require will use context.

Thanks for any help

Upvotes: 3

Views: 2098

Answers (2)

Adrien Clerc
Adrien Clerc

Reputation: 2797

UPDATE: With NPM package

Install it: yarn add webpack-ignore-dynamic-require

Enable it

// webpack.config.js

const IgnoreDynamicRequire = require('webpack-ignore-dynamic-require');

module.exports = {
  // ...
  plugins: [
    new IgnoreDynamicRequire()
  ]
}

Original answer: self made

In order to bundle a server application, I needed it, because it uses require for loading local JSON files.

Based on self answer from Patrik Holčák, I was able to create a plugin for Webpack 4. It may work on Webpack 5.

class IgnoreDynamicRequire {
  apply(compiler) {
    compiler.hooks.normalModuleFactory.tap('IgnoreDynamicRequire', factory => {
      factory.hooks.parser.for('javascript/auto').tap('IgnoreDynamicRequire', (parser, options) => {
        parser.hooks.call.for('require').tap('IgnoreDynamicRequire', expression => {
          // This is a SyncBailHook, so returning anything stops the parser, and nothing allows to continue
          if (expression.arguments.length !== 1 || expression.arguments[0].type === 'Literal') {
            return
          }
          const arg = parser.evaluateExpression(expression.arguments[0])
          if (!arg.isString() && !arg.isConditional()) {
            return true;
          }
        });
      });
    });
  }
}

This is much more complicated than the previous answer, but we need to access the parser object. After that, simply include it in your plugins array:

plugins: [
    new IgnoreDynamicRequire()
  ]

And all require calls that does not resolve to a string are left as-is.

Upvotes: 2

Patrik Holčák
Patrik Holčák

Reputation: 146

For anyone wondering: you can solve it with this plugin:

function() {
  this.parser.plugin('call require', function(expr) {
    if (expr.arguments.length !== 1) {
      return;
    }

    const param = this.evaluateExpression(expr.arguments[0]);
    if (!param.isString() && !param.isConditional()) {
      return true;
    }
  });
}

Anything that cannot be resolved by webpack will be left as is.

Upvotes: 3

Related Questions