在Webpack中,如何使用自定义源模板来构造特定的模块

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

本文关键字:模块 Webpack 何使用 自定义      更新时间:2023-09-26

我知道Webpack内部的一些功能。一些关于依赖关系、模板和模块构建的内容。然而,在其源代码中几乎没有评论,目前也没有完整的文档站点。所以,我不能把它们都串起来处理我的问题。

根据我目前的需求,我需要使用自定义源模板(类似于webpack中的MultiModule)来呈现特定模块。

注意:需要明确的是,生成的模块的依赖数组不是静态的。例如,一次它可以是['./a', './b', './c'],另一次它可能是['./b', './c','./d']。这取决于构建前的一些动态配置。

对于更详细的示例,我需要一个名为main.js的模块。在构建时,它需要动态生成目标依赖项,例如(因为不确定哪些模块是依赖项):

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

事实上,如果我只需要动态地require它们,我就可以动态地构建一个入口点。

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

它(webpack)将生成一个模块,可能是这样的:

__webpack_require__(1);
__webpack_require__(2);
__webpack_require__(3);
return __webpack_require__(4);

但我需要做更多的事情

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;

正如你们了解webpack的人一样,它非常非常复杂,很难理解和跟踪其插件(事件)层次结构。

需要一些专业知识!:)


对不起,我之前的问题不清楚。

然而,这里有一种奇怪的气氛。我设立了一个赏金来获得关注和指导。不知怎么的,某人的自由回答驱使我发表了不礼貌的评论。然后,一些和事佬带着与问题或答案无关的评论出现了。太糟糕了。

专注于这件事只会让事情变得更糟,没有任何帮助。不放手只意味着有人心胸狭窄。

要么缺乏关注,要么缺乏专家,我必须自己与之斗争。幸运的是,深入研究webpack取得了一些进展。

先决条件

在流行的webpack的前一天,有一些流行的方式,比如grunt和gullow来构建自定义构建流(使用它们的插件)。它们可以实现大部分自定义需求,尤其是生成自定义模块(webpack没有明显而直接的方法来处理)。

当您开始执行诸如自动收集自定义依赖项之类的操作时,生成自定义模块是下一个重要步骤。它可以在产品线/系列设计中常见。

解决方案

#1

这是最简单直接的方法,但缺乏灵活性。

MultiModule的source方法是生成具有多依赖关系的入口模块。只要压倒它就会击中目标。

// 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(""));
}

在第五行,我添加了一个字符串语句"hello world;"'n,其他内容都没有改变。

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

输出CCD_ 8可能看起来像:

//...
/* 0 */
/*!******************!*'
  !*** multi main ***!
  '******************/
/***/ function(module, exports, __webpack_require__) {
    "hello world";
    __webpack_require__(/*! ./a */1);
    module.exports = __webpack_require__(/*! ./b */2);
/***/ }
//...

现在,我们可以使用源方法执行我们想要的操作,同时考虑兼容性。

#2

这种方式更加灵活,但也很复杂。

它至少需要5个文件(源太长,我把它们做成了片段):

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(),
      ...
  ]
  ...
};

通过上面的代码,我们可以实现与#1相同的功能。如果我们愿意的话,我们可以对目标条目或其他需求进行更多的控制。

通常在webpack中,你只需要一个文件,可能需要文件所依赖的不同库。如果你需要main,那么webpack将基于CommonJS语法来解决依赖关系,你可以在这里阅读。删除webpack.config.js文件中的额外要求能解决这个问题吗?例如,只有以下内容作为配置:

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

听起来你并不真正理解webpack是如何工作的——它的想法是模仿Node的CommonJS语法如何允许你的javascript模块化并放置在单独的文件中,同时又具有高性能,不需要浏览器发出大量AJAX请求。如果您想了解更多关于Webpack配置文件的信息,请查看此页。


顺便说一句,在模块末尾返回绝对没有任何作用。如果您想导出,可以使用module.exports,但在main.js文件的末尾有一行类似return true之类的内容不会有任何意义。