初探webpack之编写plugin (2)

之后在webpack.config.js中进行配置,简单配置一下相关的输入输出和压缩信息,另外如果要是想每次打包删除dist文件夹的话可以考虑使用clean-webpack-plugin插件。

const path = require("path"); const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = { mode: process.env.NODE_ENV, entry: "./src/index.js", output: { filename: "index.js", path:path.resolve(__dirname, "dist") }, plugins:[ new HtmlWebpackPlugin({ title: "Webpack Template", filename: "index.html", // 打包出来的文件名 根路径是`module.exports.output.path` template: path.resolve("./public/index.html"), hash: true, // 在引用资源的后面增加`hash`戳 minify: { collapseWhitespace: true, removeAttributeQuotes: true, minifyCSS: true, minifyJS: true, }, inject: "body", // `head`、`body`、`true`、`false` scriptLoading: "blocking" // `blocking`、`defer` }) ] } 编写插件

之后到了正文环节,此时我们要编写一个插件去处理上边提到的需求,具体实现来看,我们需要的是首先在html中留下一个类似于<!-- inject:name="head" -->的标记注释,之后在webpack打包时对于html文件进行一次正则匹配,将注释相关的信息替换成页面片,通过name进行区分到底要加载哪一个页面片。另外个人感觉实际上编写webpack插件的时候还是首先参考其他人编写的webpack插件的实现,自己去翻阅文档成本查阅各种hook的成本有点高。
对于这个插件我们直接在根目录建立一个static-page-slice.js,插件由一个构造函数实例化出来,构造函数定义apply方法,在webpack处理插件的时候,apply方法会被webpack compiler调用一次。apply方法可以接收一个webpack compiler对象的引用,从而可以在回调函数中访问到compiler对象。一个最基础的Plugin的结构是类似于这样的:

class BasicPlugin{ // 在构造函数中获取用户给该插件传入的配置 constructor(options){ this.options = options || {}; } // `Webpack`会调用`BasicPlugin`实例的`apply`方法给插件实例传入`compiler`对象 apply(compiler){ compiler.hooks.someHook.tap("BasicPlugin", (params) => { /* ... */ }); } } // 导出 Plugin module.exports = BasicPlugin;

在开发plugin时最常用的两个对象就是compiler和compilation,它们是plugin和webpack之间的桥梁,compiler和compilation的含义如下:

compiler对象包含了webpack环境所有的的配置信息,包含options、loaders、plugins这些信息,这个对象在webpack启动时候被实例化,它是全局唯一的,可以简单地把它理解为webpack实例。

compilation对象包含了当前的模块资源、编译生成资源、变化的文件等,当webpack以开发模式运行时,每当检测到一个文件变化,一次新的compilation将被创建,compilation对象也提供了很多事件回调供插件做扩展,通过compilation也能读取到compiler对象。

compiler和compilation的区别在于: compiler代表了整个webpack从启动到关闭的生命周期,而compilation只是代表了一次新的编译,与之相关的信息可以参考https://webpack.docschina.org/api/compiler-hooks/。
webpack就像一条生产线,要经过一系列处理流程后才能将源文件转换成输出结果,这条生产线上的每个处理流程的职责都是单一的,多个流程之间有存在依赖关系,只有完成当前处理后才能交给下一个流程去处理,插件就像是一个插入到生产线中的一个功能,在特定的时机对生产线上的资源做处理,webpack通过tapable来组织这条复杂的生产线https://github.com/webpack/tapable。
在这里我们选择在compiler钩子的emit时期处理资源文件,即是在输出asset到output目录之前执行,在此时要注意emit是一个AsyncSeriesHook也就是异步的hook,所以我们需要使用Tapable的tapAsync或者tapPromise,如果选取的是同步的hook,则可以使用tap。

class StaticPageSlice { constructor(options) { this.options = options || {}; } apply(compiler) { compiler.hooks.emit.tapPromise("StaticPageSlice", compilation => { return new Promise(resolve => { console.log("StaticPageSlice is being called") resolve(); }) }); } } module.exports = StaticPageSlice;

接下来我们正式开始处理逻辑,首先此处我们需要先判断这个文件的类型,我们只需要处理html文件,所以我们需要先一下是否为html文件,之后就是一个正则匹配的过程,匹配到注释信息以后,将其替换为页面片,这里的页面片我们就直接在此处使用Promise模拟一下异步过程就好,之后便可以在webpack中引用并成功打包了。

// static-page-slice.js const simulateRemoteData = key => { const data = { header: "<div>HEADER</div>", footer: "<div>FOOTER</div>", } return Promise.resolve(data[key]); } class StaticPageSlice { constructor(options) { this.options = options || {}; // 传递参数 } apply(compiler) { compiler.hooks.emit.tapPromise("StaticPageSlice", compilation => { return new Promise(resolve => { const cache = {}; const assetKeys = Object.keys(compilation.assets); for (const key of assetKeys) { const isLastAsset = key === assetKeys[assetKeys.length - 1]; if (!/.*\.html$/.test(key)) { if (isLastAsset) resolve(); continue; } let target = compilation.assets[key].source(); const matchedValues = target.matchAll(/<!-- inject:name="(\S*?)" -->/g); // `matchAll`函数需要`Node v12.0.0`以上 const tags = []; for (const item of matchedValues) { const [tag, name] = item; tags.push({ tag, name, data: cache[name] ? cache[name] : simulateRemoteData(name), }); } Promise.all(tags.map(item => item.data)) .then(res => { res.forEach((data, index) => { const tag = tags[index].tag; const name = tags[index].name; if (!cache[name]) cache[name] = data; target = target.replace(tag, data); }); }) .then(() => { compilation.assets[key] = { source() { return target; }, size() { return this.source().length; }, }; }) .then(() => { if (isLastAsset) resolve(); }); } }); }); } } module.exports = StaticPageSlice; // webpack.config.js const path = require("path"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const StaticPageSlice = require("./static-page-slice"); module.exports = { mode: process.env.NODE_ENV, entry: "./src/index.js", output: { filename: "index.js", path:path.resolve(__dirname, "dist") }, plugins:[ new HtmlWebpackPlugin({ title: "Webpack Template", filename: "index.html", // 打包出来的文件名 根路径是`module.exports.output.path` template: path.resolve("./public/index.html"), hash: true, // 在引用资源的后面增加`hash`戳 minify: { collapseWhitespace: true, removeAttributeQuotes: true, minifyCSS: true, minifyJS: true, }, inject: "body", // `head`、`body`、`true`、`false` scriptLoading: "blocking" // `blocking`、`defer` }), new StaticPageSlice({ url: "https://www.example.com/" }) ] }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zgdgwf.html