下面手动实现一个简单的 Webpack Plugin。
搭建项目#
先创建一个 Webpack 项目
mkdir make-plugin
cd make-plugin
yarn init -y
yarn add webpack webpack-cli -D在根目录中创建 src 文件夹,并在里面中创建一个index.js文件,编写下面代码
console.log("Hello Webpack");创建 Webpack 配置文件webpack.config.js
module.exports = {
mode: "development",
entry: path.resolve(__dirname, "src/index"),
output: {
path: path.resolve(__dirname, "dist"),
filename: "[name].js",
},
};在package.json中编写打包脚本命令
"scripts": {
"build": "webpack"
}尝试进行编译打包
yarn build查看 dist 文件夹里面的打包后的文件main.js,在文件倒数几行里发现下面的代码,编译成功。
eval(
"console.log('Hello Webpack')\n\n//# sourceURL=webpack:///./src/index.js?"
);编写 Plugin#
简单实现#
在根目录创建plugins文件夹,然后创建一个copyright-webpack-plugin.js文件,一般 Webpack 插件的名称都是叫xxx-webpack-plugin,我们现在遵守这个命名规则。
下面编写代码,先编写一个简单的类CopyrightWebpackPlugin,并且导出这个类。
class CopyrightWebpackPlugin {
constructor() {
console.log("插件被使用了");
}
apply(compiler) {}
}
module.exports = CopyrightWebpackPlugin;Plugin 和 Loader 有点不一样,Loader 是一个普通函数,而 Plugin 却是一个类或者是构造函数。还记得我们如何使用 Plugin 的吗?一般都是调用new来使用插件的。
现在把我们编写的 Plugin 引入的配置文件中,虽然现在这个插件并没有什么用。
const CopyrightWebpackPlugin = require("./plugins/copyright-webpack-plugin");
module.exports = {
// ...
plugins: [new CopyrightWebpackPlugin()],
};然后重新进行编译打包,你会发现在命令行中,会打印出插件被使用了的信息,说明我们编写的插件已经生效。
完善插件#
下面来完善插件的代码,先说下这个插件的功能,非常简单,就是在文件打包后,新生成一个copyright.md的文件,而文件的内容就是written by Hale。
下面开始编写类的apply方法。
class CopyrightWebpackPlugin {
// ...
apply(compiler) {
compiler.hooks.emit.tapAsync(
"CopyrightWebpackPlugin",
(compilation, cb) => {
compilation.assets["copyright.md"] = {
source: function () {
return "written by Hale";
},
size: function () {
return 15;
},
};
cb();
}
);
}
}这个方法接受compiler作为参数,compiler是 Webpack 编译后生成的文件,里面包含了很多内容,包括配置、钩子 hooks 等。这里使用hooks钩子来增加文件。compiler的 hooks 和前端框架Vue、React的生命函数的钩子类似,都是在某一个时刻执行的函数。
钩子的emit方法是在生成打包文件前执行的,我们使用这个方法在打包前创建一个copyright.md文件。
因为这个钩子是异步执行的,所以使用tapAsync方法来调用钩子,需要转入两个参数,第一个参数是本身插件的名称,第二个参数是一个函数。函数需要传入compilation参数,这个参数也包含了很多属性。因为这是一个异步的钩子,所以还需要传入函数的第二个参数cb,这是一个回调函数,在这个钩子函数结束时调用它。
compilation的assets属性表示生成后的文件,我们可以在赋值前打印下这个属性
(compilation, cb) => {
console.log(compilation.assets); // ! 打印属性
compilation.assets["copyright.md"] = {
// ...
};
};生成的内容如下,这里显示了生成的main.js文件的一些属性
{ 'main.js':
CachedSource {
_source: ConcatSource { children: [Array] },
_cachedSource: undefined,
_cachedSize: undefined,
_cachedMaps: {},
node: [Function],
listMap: [Function]
}
}现在我们就可以添加一个文件
compilation.assets["copyright.md"] = {
source: function () {
return "written by Hale";
},
size: function () {
return 15;
},
};其中source是源码文件,而size是生成文件的大小,返回 15 表示生成的文件大小是 15 byte。
插件编写完成后,重新进行编译打包,就会发现dist文化夹中新增加了一个copyright.md文件,内容是written by Hale,说明我们编写的这个插件生效了。
其实,插件可以使用多个钩子,下面使用一个同步的钩子compile,这个钩子是在 Webpack 初始化编译时执行的,执行的顺序还在emit钩子之前。因为这是一个同步执行的钩子,所以使用tap来调用,并且在第二个函数参数中也不需要再传入回调函数cb。
class CopyrightWebpackPlugin {
apply(compiler) {
compiler.hooks.compile.tap("CopyrightWebpackPlugin", (compilation) => {
console.log("compile hook"); // compile 钩子的打印信息
});
compiler.hooks.emit.tapAsync(
"CopyrightWebpackPlugin",
(compilation, cb) => {
console.log("emit hook"); // emit 钩子的打印信息
compilation.assets["copyright.md"] = {
source: function () {
return "written by Hale";
},
size: function () {
return 15;
},
};
cb();
}
);
}
}重新进行编译打包,编译成功。然后,我们发现在命令行中是先打印出compile hook信息,然后再打印出emit hook信息。
Node 调试#
我们在初次编写插件时,不知道插件中一些的参数是什么样子的,可以使用console.log在命令行把参数的信息打印出来查看,也可以使用 Node 的调试模式进行断点调试,来查看它的参数的属性。
下面我们使用 Node 调试模式调试代码
在package.json新增debug脚本命令,这里调用node命令进行 debug 调试。
"scripts": {
"debug": "node --inspect --inspect-brk node_modules/webpack/bin/webpack.js",
}其中node node_modules/webpack/bin/webpack.js和webpack这两个命令效果是一样的。
然后加入参数,--inspect表示进入调试模式 ,而--inspect-brk参数表示在调试代码的第一行打断点。
下面执行这个命令,进入 Node 调试模式
yarn debug命令行中会出现下面这些提示信息
$ node --inspect --inspect-brk node_modules/webpack/bin/webpack.js
Debugger listening on ws://127.0.0.1:9229/cf7f577c-b23f-417a-813e-c42299328113
For help, see: https://nodejs.org/en/docs/inspector
Debugger attached.这时,打开chrome浏览器,在地址栏输入127.0.0.1:9229按回车,然后按下F12打开页面的控制台,发现控制面板的左上角有个绿色的 Node 的 Logo 标识,点击这个标识就可以进入到代码的调试界面。
此时,我们发现代码已经在 Webpack 模块的第一行代码中停下来,因为我们前面设置了在第一行打断点。这时我们就可以进行调试了。
从一开始就在 Webpack 模块的第一行代码中停下来并没有什么意义。因为我们还需要跳到插件的代码中去查看属性,这样需要花费不少时间进行跳转。所以,我们可以手动在插件代码中新增一个断点,然后直接跳这行代码中去调试。
怎么新增断点呢?在插件中插入一句debugger代码,这样相当于在插件的这行代码中打了一个断点。
class CopyrightWebpackPlugin {
apply(compiler) {
compiler.hooks.compile.tap("CopyrightWebpackPlugin", (compilation) => {
console.log("compile hook");
});
compiler.hooks.emit.tapAsync(
"CopyrightWebpackPlugin",
(compilation, cb) => {
console.log("emit hook");
debugger; // debugger 断点
compilation.assets["copyright.md"] = {
source: function () {
return "written by Hale";
},
size: function () {
return 15;
},
};
cb();
}
);
}
}重新启动 Node 的调试模式
yarn debug再次打开调试页面,然后点击跳转按钮,或者按F8直接跳到插件代码中的debugger这一行上。这时,把鼠标移动到相关的代码上面就可以查看它们的属性信息了。