如何使用 webpack 内置钩子自定义添加复杂的多入口

343 阅读2分钟

参考文章

一、如何使用 webpack 内置钩子自定义添加复杂的多入口?

为什么要利用内置钩子去实现动态的入口呢?我的理解是比如在将小程序项目以webpack工程化时,如果仅仅是在webpack.config.js中去配置 entry 的话,那么在新增页面之后,不能够watch到变化,需要重启编译。

明确思路就是手动找到所有的入口文件路径数组,调用webpack内置插件EntryPlugin插件去添加即可

// 首先挂载获取入口路径的事件方法到处理入口的钩子中,
// 仿造EnteyOptionPlugin:
import { EntryPlugin } from 'webpack'

apply (compiler) {
    const { context, entry } = compiler.options;
    compiler.hooks.entryOption.tap("MyEntryPlugin", (context, entry) => {
        // 自己获取的多入口地址数组
        const entryPath = ['pages\home\index.ts', ...] // 伪代码
        entryPath.forEach(pathItem => {
            const replaceExtpathItem = someReplaceFun('pages\home\index.ts') // pages\home\index
            new EntryPlugin(context, pathItem, replaceExtpathItem).apply(compiler);
        })
        return true;
    });
}

梳理 webpack 处理入口流程如下:

// webpack.js
new WebpackOptionsApply().process(options, compiler)

// WebpackOptionsApply.js
new EntryOptionPlugin().apply(compiler);
compiler.hooks.entryOption.call(options.context, options.entry);

// EntryOptionPlugin.js
const EntryPlugin = require("./EntryPlugin")
...
new EntryPlugin(context, entry, options).apply(compiler);


// EntryPlugin.js
// 在完成编译之前执行 Compiler创建了compilation之后触发了此钩子
compiler.hooks.make.tapAsync("EntryPlugin", (compilation, callback) => {
	compilation.addEntry(context, dep, options, err => {
		callback(err);
	});
});



// Compilation.js
addEntry(context, entry, optionsOrName, callback) {
	// TODO webpack 6 remove
	const options =
		typeof optionsOrName === "object"
			? optionsOrName
			: { name: optionsOrName };

	this._addEntryItem(context, entry, "dependencies", options, callback);
}

...

_addEntryItem(context, entry, target, options, callback) {
	// ...
	this.hooks.addEntry.call(entry, options);
	// ...
}

因此可以在以下两处任选一处添加处理钩子事件

  • compiler.hooks.entryOption
  • compiler.hooks.make.tapAsync

二、webpack 以自定的入口为主?

1.用户的配置中的plugin会先于webpack内置plugin执行

// webpack.js
if (Array.isArray(options.plugins)) {
    // 运行用户配置插件
    for (const plugin of options.plugins) {
        if (typeof plugin === "function") {
            plugin.call(compiler, compiler);
        } else {
            plugin.apply(compiler);
        }
    }
}

// 应用webpack默认配置
applyWebpackOptionsDefaults(options);
...

// 根据用户配置的webpack选项,调用内置插件
// 从上面流程分析中可知此方法调用了WebpackOptionsApply.js 的 new EntryOptionPlugin().apply(compiler)来走webpack默认的入口流程
new WebpackOptionsApply().process(options, compiler);

2.SyncBailHook 是一个同步的、保险类型的 Hook,意思是只要其中一个有返回了,后面的就不执行了

// Compiler.js
this.hooks = Object.freeze({
 ...
 entryOption: new SyncBailHook(["context", "entry"])
 ...
})

因此只需要手动挂载到entryOption钩子上的事件最后以return结尾就可以了

entryOption.tap('UserCustomPluginName', () => {
    // 获取入口路径
    ...
    return true
}

基于以上两个条件我们在自定义入口处理方法中最后return就能以自定义的入口为最终确定的入口