Webpack中手动实现Loader与Plugin
Loader
loader 是一个转换器,用于对源代码进行转换。
工作流程
- webpack.config.js 里配置了一个 模块 的 Loader;
2.遇到 相应模块 文件时,触发了 该模块的 loader;
3.loader 接受了一个表示该 模块 文件内容的 source;
4.loader 使用 webapck 提供的一系列 api 对 source 进行转换,得到一个 result;
5.将 result 返回或者传递给下一个 Loader,直到处理完毕。
举个例子。
loader.js (将所有含有“小明”字符串 全都改为“老王”)
module.exports = function (source){
return source.replace('小明','老王')
}
webpack.config.js
const path = require('path')
module.exports = {
mode: 'development',
entry: {
main: './src/index.js' //入口文件
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: path.resolve('./src/loader.js'), //我们自己实现的loader
},
]
}
]
},
}
index.js(入口文件)
function test() {
console.log('你好我是小明')
}
test()
编译后等待输出结果:
bound.js
/*
* ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development").
* This devtool is neither made for production nor for readable output files.
* It uses "eval()" calls to create a separate source file in the browser devtools.
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
* or disable the default devtool with "devtool: false".
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
*/
/******/ (() => { // webpackBootstrap
/******/ var __webpack_modules__ = ({
/***/ "./src/index.js":
/*!**********************!*\
!*** ./src/index.js ***!
\**********************/
/***/ (() => {
eval("function test() {\n console.log('你好我是老王')\n}\n\ntest()\n\n\n//# sourceURL=webpack:///./src/index.js?"); //小明变成老王了
/***/ })
/******/ });
/************************************************************************/
/******/
/******/ // startup
/******/ // Load entry module and return exports
/******/ // This entry module can't be inlined because the eval devtool is used.
/******/ var __webpack_exports__ = {};
/******/ __webpack_modules__["./src/index.js"]();
/******/
/******/ })()
;
上述打包耗时:77ms
异步Loader
loader.js如下
module.exports = function (source) {
const callback = this.async()
// 由于有 3 秒延迟,所以打包时需要 3+ 秒的时间
setTimeout(() => {
callback(null, `${source.replace('小明', '老王')}`)
}, 3000)
}
上述打包耗时:3083ms
Plugin
webpack 在整个编译周期中会触发很多不同的事件,plugin 可以监听这些事件,并且可以调用 webpack 的 API 对输出资源进行处理。这是它和 loader 的不同之处,loader 一般只能对源文件代码进行转换,而 plugin 可以做得更多。plugin 在整个编译周期中都可以被调用,只要监听事件。
名词解释
对于 webpack 编译,有两个重要的对象需要了解一下:
Compiler 和 Compilation
在插件开发中最重要的两个资源就是 compiler 和 compilation 对象。理解它们的角色是扩展 webpack 引擎重要的第一步。
compiler 对象代表了完整的 webpack 环境配置。这个对象在启动 webpack 时被一次性建立,并配置好所有可操作的设置,包括 options,loader 和 plugin。当在 webpack 环境中应用一个插件时,插件将收到此 compiler 对象的引用。可以使用它来访问 webpack 的主环境。
compilation 对象代表了一次资源版本构建。当运行 webpack 开发环境中间件时,每当检测到一个文件变化,就会创建一个新的 compilation,从而生成一组新的编译资源。一个 compilation 对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息。compilation 对象也提供了很多关键时机的回调,以供插件做自定义处理时选择使用。
组成部分:
- 一个 JavaScript 命名函数。
- 在插件函数的 prototype 上定义一个 apply 方法。
- 指定一个绑定到 webpack 自身的事件钩子。
- 处理 webpack 内部实例的特定数据。
- 功能完成后调用 webpack 提供的回调
举个例子
plugin.js
function Plugin(options) { }
Plugin.prototype.apply = function (compiler) {
// 所有文件资源都被 loader 处理后触发这个事件
compiler.plugin('emit', function (compilation, callback) {
// 功能完成后调用 webpack 提供的回调
console.log('Hello World')
callback()
})
}
module.exports = Plugin
使用:
const Plugin = require('./src/plugin')
module.exports = {
...
plugins: [
new Plugin()
]
}
更复杂的例子:
经过 loader 处理后的打包文件 bundle.js 引入到 index.html 中:
function Plugin(options) { }
Plugin.prototype.apply = function (compiler) {
// 所有文件资源经过不同的 loader 处理后触发这个事件
compiler.plugin('emit', function (compilation, callback) {
// 获取打包后的 js 文件名
const filename = compiler.options.output.filename
// 生成一个 index.html 并引入打包后的 js 文件
const html = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="${filename}"></script>
</head>
<body>
</body>
</html>`
// 所有处理后的资源都放在 compilation.assets 中
// 添加一个 index.html 文件
compilation.assets['index.html'] = {
source: function () {
return html
},
size: function () {
return html.length
}
}
// 功能完成后调用 webpack 提供的回调
callback()
})
}
module.exports = Plugin
使用方式如上
结果:会生成一个html并且自动引入了bound.js
长风破浪会有时,直挂云帆济沧海