e-cloud
e-cloud

Reputation: 4481

In Webpack, how to use custom source template to construct specific module

I know some inner functionality inside Webpack. Something about dependencies, template, and module building. However, there is little comment inside its source and no full document site for now. So, i can't chain them all to deal with my problem.

With my current requirement, i need to render specific module with custom source template (similar to this MultiModule in webpack).

Note: To be clear, the generated module's dependency array is not static. For example, one time it may be ['./a', './b', './c'], another time it may be ['./b', './c','./d']. That is up to some dynamic config before build.

For more detail example, i need a module call main.js. In build time, it need to be dynamically generated with target dependencies like(for being not sure which modules would be dependencies):

// main.js
var a = require('./a')
var b = require('./b')
var c = require('./c')
var d = require('./d')
...

In fact, if i only need to dynamically require them all, i can just construct an entry point dynamically.

// webpack.config.js
{
    entry: {
        main: [
            './a',
            './b',
            './c',
            ...
        ]
    },
}

and it(webpack) will generate a module may like this:

__webpack_require__(1);
__webpack_require__(2);
__webpack_require__(3);

return __webpack_require__(4);

But i need to do something more:

var a = __webpack_require__(1);
var b = __webpack_require__(2);
var c = __webpack_require__(3);
var d = __webpack_require__(4);
...

// do something with a,b,c,d... under my custom need
...

return somthing or nothing;

As you guys who know about webpack, it's very very complicated and hard to understand and track its plugin(event) hierarchy.

Need some expertise! :)


I'm sorry foy my unclear question before.

However, there is some kind of weird atmosphere. I set up a bounty for attention and guidance. Someone's free-minded answer drove me to make comment with impoliteness somehow. And then some peacemaker shows up with comments unrelated to the question or answer. That sucks.

Focusing on that thing just makes things worse and nothing helped. Not letting it go just means someone has petty mind.

Upvotes: 0

Views: 991

Answers (2)

e-cloud
e-cloud

Reputation: 4481

Either lacking attention and lacking expert or not, I have to fight it myself. Fortunately, digging into webpack makes some progress.

Prerequisite

The day before popular webpack, there are fashions like grunt and gulp to construct a custom build flow (with their plugins). They can achieve most of custom requirement, especially generating a custom module(which webpack doesn't have obvious and direct way to deal with).

when you come to do something like automatic collecting custom dependencies, then generating a custom module is the next essential step. It can be commonly seen in product line/family design.

Solutions

#1

This is the simplest and direct way but lack of flexibility.

The source method of MultiModule is to generate the entry module with multi-dependencies. Just overriding it will hit the target.

// hack.js
const MultiModule = require('webpack/lib/MultiModule')

MultiModule.prototype.source = function(dependencyTemplates, outputOptions) {
    var str = ['"hello world";\n'];
    this.dependencies.forEach(function (dep, idx) {
        if (dep.module) {
            if (idx === this.dependencies.length - 1)
                str.push("module.exports = ");
            str.push("__webpack_require__(");
            if (outputOptions.pathinfo)
                str.push("/*! " + dep.request + " */");
            str.push("" + JSON.stringify(dep.module.id));
            str.push(")");
        } else {
            str.push("(function webpackMissingModule() { throw new Error(");
            str.push(JSON.stringify("Cannot find module \"" + dep.request + "\""));
            str.push("); }())");
        }
        str.push(";\n");
    }, this);

    return new RawSource(str.join(""));
}

At the fifth line, i add a string statement "hello world;"\n, nothing else changed.

module.exports = {
    entry: {
        main: ["./a", "./b"],
    }
    // something else
}

the output main.js may look like:

//...
/* 0 */
/*!******************!*\
  !*** multi main ***!
  \******************/
/***/ function(module, exports, __webpack_require__) {

    "hello world";
    __webpack_require__(/*! ./a */1);
    module.exports = __webpack_require__(/*! ./b */2);

/***/ }
//...

Now we can do what we want with the source method, with the compatibility in mind.

#2

This way is much more flexible but also complex.

It requires at lease 5 files(sources are too long, I made them into snippets):

CustomMultiModule.js:

// CustomMultiModule.js
const MultiModule = require('webpack/lib/MultiModule')
const RawSource = require('webpack/lib/RawSource')

class CustomMultiModule extends MultiModule {
  constructor(...args) {
    super(...args)
  }

  source(dependencyTemplates, outputOptions) {
    var str = ['"hello world";'];
    this.dependencies.forEach(function(dep, idx) {
      if (dep.module) {
        if (idx === this.dependencies.length - 1)
          str.push("module.exports = ");
        str.push("__webpack_require__(");
        if (outputOptions.pathinfo)
          str.push("/*! " + dep.request + " */");
        str.push("" + JSON.stringify(dep.module.id));
        str.push(")");
      } else {
        str.push("(function webpackMissingModule() { throw new Error(");
        str.push(JSON.stringify("Cannot find module \"" + dep.request + "\""));
        str.push("); }())");
      }
      str.push(";\n");
    }, this);

    return new RawSource(str.join(""));
  }
}

module.exports = CustomMultiModule

CustomMultiModuleFactory.js:

// CustomMultiModuleFactory.js
const MultiModuleFactory = require('webpack/lib/MultiModuleFactory')
const CustomMultiModule = require('./CustomMultiModule')

class CustomMultiModuleFactory extends MultiModuleFactory {
  constructor() {
    super()
  }

  create(context, dependency, callback) {
    callback(null, new CustomMultiModule(context, dependency.dependencies, dependency.name));
  };
}

module.exports = CustomMultiModuleFactory

CustomMultiEntryPlugin.js:

// CustomMultiEntryPlugin.js
const MultiEntryPlugin = require('webpack/lib/MultiEntryPlugin')
const MultiEntryDependency = require('webpack/lib/dependencies/MultiEntryDependency')
const CustomMultiModuleFactory = require('./CustomMultiModuleFactory')

class CustomMultiEntryPlugin extends MultiEntryPlugin {
  constructor(context, entries, name) {
    super(context, entries, name)
  }

  apply(compiler) {
    compiler.plugin('after-plugins', function(compiler) {
      compiler.plugin("compilation", function(compilation, params) {
        var multiModuleFactory = new CustomMultiModuleFactory();

        compilation.dependencyFactories.set(MultiEntryDependency, multiModuleFactory);
      })
    })
  }
}

module.exports = CustomMultiEntryPlugin

CustomEntryOptionPlugin.js:

// CustomEntryOptionPlugin.js
const CustomMultiEntryPlugin = require('./CustomMultiEntryPlugin')

class CustomEntryOptionPlugin {
  constructor() {}

  apply(compiler) {
    compiler.plugin("entry-option", function(context, entry) {
      if (typeof entry === "object") {
        Object.keys(entry).forEach(function(name) {
          if (Array.isArray(entry[name])) {
            compiler.apply(new CustomMultiEntryPlugin(context, entry[name], name));
          }
        });
      }
    });
  }
}

module.exports = CustomEntryOptionPlugin

webpack.config.js:

// webpack.config.js
const CustomEntryOptionPlugin = require('./CustomEntryOptionPlugin')

module.exports = {
  entry: {
    main: ["./a", "/b"] // this dependencies array may be generated
      ...
  },
  output: {
    path: path.join(__dirname, "js"),
    pathinfo: true,
    filename: "[name].[chunkhash].js",
    chunkFilename: "[chunkhash].js"
  }
  plugins: [
      new CustomEntryOptionPlugin(),
      ...
  ]
  ...
};

With the code above, we can achieve the same as #1. And we can gain more control over the target entry or other requirements, if we want.

Upvotes: 2

Jay
Jay

Reputation: 1056

Often in webpack you're only requiring one file, and maybe different libs that the files depend on. If you require main, then webpack is going to resolve the dependencies based on the CommonJS syntax which you can read about here. Does removing the extra requirements in your webpack.config.js file solve this? e.g. having only the following as the config:

// webpack.config.js
{
  entry: [ "./main" ],
  ...
}

It sounds like you don't really understand how webpack works-- the idea of it is to emulate how Node's CommonJS syntax allows your javascript to be modular and placed in separate files, while also being performant and not requiring tons of AJAX requests by your browser. If you want to read more about Webpack's config file, check out this page.


As a side note, returning at the end of the module does absolutely nothing. If you want to export, you can use module.exports, but having a line like return true or something at the end of your main.js file doesn't get caught anywhere meaningful.

Upvotes: 0

Related Questions