Aaronius
Aaronius

Reputation: 4953

Dynamically require an aliased module using webpack

I'm configuring a bunch of module aliases through webpack via resolve.alias. Then, in my app code, I would like to require one of these modules using a variable that contains an alias name:

var module = require(moduleAlias);

Unfortunately, this creates a "context module" containing everything in the script's directory and its descendants which isn't what I was after in this particular case. Also, since nowhere in my code am I explicitly requiring all the aliased modules, they don't get built into my app.

Two questions:

  1. How do I make sure all aliased modules are bundled with my code?
  2. How do I access them using a variable that contains an alias?

Thanks!

Upvotes: 9

Views: 6202

Answers (2)

InfinteScroll
InfinteScroll

Reputation: 684

The cleanest solution I've found is to overwrite the default module Id system. Webpack seems to use array index by default. I do a check to see if the file path is in my alias module, then set its id to that.

That way in my code where I needed to do synchronous dynamic requires with an alias, I could do __webpack_require__(alias)

This is a total hack using private methods (__webpack_require__), but I see this as a temporary fix until I can migrate our codebase to either properly async dynamic require or properly use paths instead of alias everywhere like many requireJS codebases do.

var path = require('path');
var _ = require('lodash');

function NamedAliasModules(){};    

NamedAliasModules.prototype.apply = function(compiler){
    compiler.plugin('compilation', function(compilation){
        compilation.plugin("before-module-ids", function(modules) {
            modules.forEach(function(module) {
                if(module.id === null && module.libIdent) {
                    var id = module.libIdent({
                        context: compiler.options.context
                    });
                    var fullpath = path.resolve(__dirname, id);

                    if (_.has(aliasLookup, fullpath) || _.has(aliasLookup, fullpath.replace(/\.js$/,''))){
                        id = aliasLookup[fullpath] || aliasLookup[fullpath.replace(/\.js$/, '')];

                        module.libIdent = function(){
                            return id;
                        }

                    }
                    module.id = id;
                }
            }, this);
        }.bind(this));
    }.bind(this));
}

Upvotes: 6

JBE
JBE

Reputation: 12607

This only answers the second part of your question: if you have bundled module with alias and you want those aliases to be requirable from a context:

To my knowledge, there is no official way of doing it with webpack. I created a plugin, working with Node 4 (you can adapt if you want to use pure ES5), that will add to any context the list of aliases:

'use strict';

class AddToContextPlugin {
  constructor(extras) {
    this.extras = extras || [];
  }

  apply(compiler) {
    compiler.plugin('context-module-factory', (cmf) => {
      cmf.plugin('after-resolve', (result, callback) => {
        this.newContext = true;
        return callback(null, result);
      });

      // this method is called for every path in the ctx
      // we just add our extras the first call
      cmf.plugin('alternatives', (result, callback) => {
        if (this.newContext) {
          this.newContext = false;

          const extras = this.extras.map((ext) => {
            return {
              context: result[0].context,
              request: ext
            };
          });

          result.push.apply(result, extras);
        }
        return callback(null, result);
      });
    });
  }
}

module.exports = AddToContextPlugin;

This is how you can use it:

webpack({
      /*...*/
      resolve: {
        alias: {
          'alias1': 'absolute-path-to-rsc1',
          'alias2$': 'absolute-path-to-rsc2'
        }
      },
      plugins: [
        new AddToContextPlugin(['alias1', 'alias2'])
      ]
    })

And it result as the following code generated:

function(module, exports, __webpack_require__) {

    var map = {
        "./path/to/a/rsc": 2,
        "./path/to/a/rsc.js": 2,
        "./path/to/another/rsc.js": 301,
        "./path/to/another/rsc.js": 301,
        "alias1": 80,
        "alias2": 677
    };
    function webpackContext(req) {
        return __webpack_require__(webpackContextResolve(req));
    };
    function webpackContextResolve(req) {
        return map[req] || (function() { throw new Error("Cannot find module '" + req + "'.") }());
    };
    webpackContext.keys = function webpackContextKeys() {
        return Object.keys(map);
    };
    webpackContext.resolve = webpackContextResolve;
    module.exports = webpackContext;
    webpackContext.id = 1;

}

Upvotes: 10

Related Questions