webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个 依赖图(dependency graph),然后将你项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源,用于展示你的内容。想要写一个webpack的自定义插件,需要先了解webpack的一些基础配置,详细内容可以在 webpack官网 学习。
-
入口(entry)
entry是指入口文件的配置项,当入口只有一个时,可以简写为:
module.exports = { entry: './src/main.js', };
webpack还支持多个入口的情况,此时需要用数组的形式或者对象的形式给entry赋值,如:
entry: { app: './src/app.js', main: './src/main.js', }
相对于简写语法,对象语法比较繁琐,但是可扩展行更高,例如用于分离应用程序和第三方库入口,或者是多页面应用程序入口配置。
-
输出(output)
输出的配置需要根据入口的配置来变换,最简单的写法是只指定output的filepath属性,这样会默认将该文件输出到项目根目录下的dist目录中,如果webpack打包后会输出文件不少于一个,还需要用占位符,例如[name],[hash]等,确保输出的文件都具有唯一名字。除此之外,还可以利用output.path指定打包后的输出路径等等。
output: { path: path.resolve(__dirname, "dist"), filename: "[name].js", },
-
loader
loader是用于对代码模块进行转换,webpack本身只支持打包js和json类型的文件,但是我们的项目中肯定不止用到这两种类型文件,例如还有css,html已经ts等等,此时就需要利用loader对这些代码进行转换。一份代码可能需要多个loader才能被转换,例如转换less样式代码:
{ test: /\.less$/, /** * use可以使用多个loader,从下往上或者从右往左执行 */ use: [ { loader: MiniCssExtractPlugin.loader, // creates link tag }, { loader: "css-loader", // translates CSS into CommonJS }, { loader: "less-loader", // compiles Less to CSS }, ], },
常用的一些loader:
-
style-loader:用于将编译好后的css样式以style标签的形式插入到html文件中
-
css-loader: 用于处理css文件,需要搭配style-loader或者MiniCssExtractPlugin使用
-
less-loader:用于处理less样式文件,需要跟css-loader搭配使用
-
postcss-loader:自动添加浏览器前缀,解决样式浏览器兼容问题
-
babel-loader:使用babel处理js代码
-
ts-loader:用于处理ts文件,需要配置tsconfig.json文件
-
url-loader:可以将文件大小小于设置的limit值的文件转成base64编码,减少请求次数,提升页面加载速度
-
plugins
接下来进入到我们的主题,插件部分,在 webpack官网插件部分 的介绍中,插件是webpack的支柱功能,插件的目的是为了解决loader解决不了的问题,由此可见,插件的功能是十分强大的,如果要使用webpack插件,需要在webpack配置中的plugins传入一个new示例,因为可以使用多个插件,对应plugins是一个数组,每添加一个插件,就需要在plugins中新增一个new示例,每个插件传递的参数由使用情况决定,例如:
plugins: [ new MyPlugin(/Test(1|2)/, /(t|j|cs)s$/), //自定义插件 new MiniCssExtractPlugin(), //将打包后的css文件以link标签的方式导入 new HtmlWebpackPlugin({ //根据传入的模板,生成一个html文件 filename: "index.html", template: "./src/index.html", }), ],
当我们在webpack打包的过程中想要实现某些操作时,如果已经存在的插件不能满足我们的需求,就需要自己开发自定义插件。
-
自定义插件
webpack插件可以是一个javascript命名函数或者一个javascript类,在类中需要实现一个apply方法,该方法的参数是compiler对象。compiler是webpack的主要引擎,它通过 CLI 或者 Node API 传递的所有选项创建出一个 compilation 实例。 它扩展(extends)自 Tapable
类,用来注册和调用插件。 大多数面向用户的插件会首先在 Compiler
上注册。compiler包含很多钩子函数,在调用钩子函数时,可以通过如下方式访问:
compiler.hooks.someHook.tap('MyPlugin', (params) => { /* ... */ });
someHook表示自己要使用的钩子函数,除tap方法外,还可以使用tapAsync方法和tapPromise方法,当使用tapAsync方法绑定插件时,就可以在钩子的回调函数中使用异步方法,但是必须在最后调用该钩子的回调函数的参数中指定的回调函数,否则会导致webpack不能继续执行,当然也有同步的钩子不支持tapAsync。在调用tapPromise,需要返回一个Promise,在异步调用完成后resolve。compiler有很多的钩子函数,如果想要知道每个钩子的具体调用位置,需要在webpack的源码中,通过搜索 hooks.<hook name>.call,查找具体使用位置。在complier的某些钩子函数的参数中,可以获取到compilation对象,Compilation
模块会被 Compiler
用来创建新的 compilation 对象(或新的 build 对象)。 compilation
实例能够访问所有的模块和它们的依赖(大部分是循环依赖)。 它会对应用程序的依赖图中所有模块, 进行字面上的编译(literal compilation)。 在编译阶段,模块会被加载(load)、封存(seal)、优化(optimize)、 分块(chunk)、哈希(hash)和重新创建(restore),compliation和compiler用法相同,某些钩子函数也可以使用tapAsync和tapPromise方法。
-
插件实例
const fs = require("fs") class MyPlugin { //构造函数,auchor代表锚点,type表示需要匹配的文件类型的数组 constructor(auchor, type) { this.auchor = auchor this.type = type } apply(compiler) { let resultArr = [] compiler.hooks.compilation.tap("MyPlugin", (compilation) => { compilation.hooks.finishModules.tap("MyPlugin", (modules) => { modules.forEach((module) => { if ( module.resource && !module.resource.includes("node_modules") && module.resource.split(".")[1]?.match(this.type) ) { console.log(module.resource) const result = matchAnchor(module.resource, this.auchor) // 对数组进行拼接; resultArr = resultArr.concat(result) } }) }) }) compiler.hooks.emit.tap("Myplugin", (compilation) => { //将匹配到的数据输出到webpack的输出目录中 compilation.assets["matchResult.json"] = { source() { return JSON.stringify(resultArr) }, size() { return resultArr.length }, } }) } } //匹配函数,返回封装好的对象数组 function matchAnchor(path, anchor) { //匹配出的结果数组,默认为空 const result = [] // 读取文件内容 const data = fs.readFileSync(path, "utf-8").toString() // 分行 const lines = data.split("\n") lines.forEach((line, index) => { //将匹配条件转化成正则 const reg = new RegExp(anchor) const matchArr = reg.exec(line) if (matchArr) { result.push({ filepath: path.replaceAll("\\", "/"), // 匹配到的文件路径 line: index + 1, // 匹配到的行号 match: matchArr[0], // 匹配到的字符串 }) } }) return result } module.exports = MyPlugin
该插件由一个js类和一个匹配函数组成,需求是找出指定文件类型中的某些字符串,需要在调用的时候传递两个参数,在类的构造函数中初始化。之后在apply方法中,实现我们想要的效果。这里用到了compiler的两个钩子函数,第一个是compilation钩子。compilation在编译创建之后执行,这是一个同步 SyncHook 钩子,通过回调函数中的compilation的finishModules钩子,可以访问到webpack构建的依赖树。之后通过module.resource,可以拿到每个模块的依赖文件的路径,之后通过文件读写操作就可以查找想要的字符串。第二个compiler钩子函数时emit,emit在输出 asset 到 output 目录之前执行,这个钩子 不会 被复制到子编译器。在该钩子函数中,通过compilation.assets,将查询到的结果一并输出到dist文件夹中。最后就是需要在webpack的配置中导入该插件,并在plugins中new一个该类的实例,而apply方法会在new该实例时自动被调用。
标签:插件,自定义,钩子,compilation,loader,webpack,compiler From: https://www.cnblogs.com/zhoushaowen/p/16891808.html