东湖南昌网站建设公司,手机网站功能,个人网站建设架构,网站的下载二维码怎么做Plugin 的作用
通过插件我们可以扩展 webpack#xff0c;加入自定义的构建行为#xff0c;使 webpack 可以执行更广泛的任务#xff0c;拥有更强的构建能力。
Plugin 工作原理 webpack 就像一条生产线#xff0c;要经过一系列处理流程后才能将源文件转换成输出结果。 这条…Plugin 的作用
通过插件我们可以扩展 webpack加入自定义的构建行为使 webpack 可以执行更广泛的任务拥有更强的构建能力。
Plugin 工作原理 webpack 就像一条生产线要经过一系列处理流程后才能将源文件转换成输出结果。 这条生产线上的每个处理流程的职责都是单一的多个流程之间有存在依赖关系只有完成当前处理后才能交给下一个流程去处理。 插件就像是一个插入到生产线中的一个功能在特定的时机对生产线上的资源做处理。webpack 通过 Tapable 来组织这条复杂的生产线。 webpack 在运行过程中会广播事件插件只需要监听它所关心的事件就能加入到这条生产线中去改变生产线的运作。 webpack 的事件流机制保证了插件的有序性使得整个系统扩展性很好。 ——「深入浅出 Webpack」 站在代码逻辑的角度就是webpack 在编译代码过程中会触发一系列 Tapable 钩子事件插件所做的就是找到相应的钩子往上面挂上自己的任务也就是注册事件这样当 webpack 构建的时候插件注册的事件就会随着钩子的触发而执行了。
Webpack 内部的钩子
什么是钩子
钩子的本质就是事件。为了方便我们直接介入和控制编译过程webpack 把编译过程中触发的各类关键事件封装成事件接口暴露了出来。这些接口被很形象地称做hooks钩子。开发插件离不开这些钩子。
Tapable
Tapable 为 webpack 提供了统一的插件接口钩子类型定义它是 webpack 的核心功能库。webpack 中目前有十种 hooks在 Tapable 源码中可以看到他们是
// https://github.com/webpack/tapable/blob/master/lib/index.js
exports.SyncHook require(./SyncHook);
exports.SyncBailHook require(./SyncBailHook);
exports.SyncWaterfallHook require(./SyncWaterfallHook);
exports.SyncLoopHook require(./SyncLoopHook);
exports.AsyncParallelHook require(./AsyncParallelHook);
exports.AsyncParallelBailHook require(./AsyncParallelBailHook);
exports.AsyncSeriesHook require(./AsyncSeriesHook);
exports.AsyncSeriesBailHook require(./AsyncSeriesBailHook);
exports.AsyncSeriesLoopHook require(./AsyncSeriesLoopHook);
exports.AsyncSeriesWaterfallHook require(./AsyncSeriesWaterfallHook);
exports.HookMap require(./HookMap);
exports.MultiHook require(./MultiHook);
Plugin 构建对象
Compiler
compiler 对象中保存着完整的 Webpack 环境配置每次启动 webpack 构建时它都是一个独一无二仅仅会创建一次的对象。
这个对象会在首次启动 Webpack 时创建我们可以通过 compiler 对象上访问到 Webapck 的主环境配置比如 loader 、 plugin 等等配置信息。
它有以下主要属性
compiler.options 可以访问本次启动 webpack 时候所有的配置文件包括但不限于 loaders 、 entry 、 output 、 plugin 等等完整配置信息。compiler.inputFileSystem 和 compiler.outputFileSystem 可以进行文件操作相当于 Nodejs 中 fs。compiler.hooks 可以注册 tapable 的不同种类 Hook从而可以在 compiler 生命周期中植入不同的逻辑。
compiler hooks 文档
Compilation
compilation 对象代表一次资源的构建compilation 实例能够访问所有的模块和它们的依赖。
一个 compilation 对象会对构建依赖图中所有模块进行编译。 在编译阶段模块会被加载(load)、封存(seal)、优化(optimize)、 分块(chunk)、哈希(hash)和重新创建(restore)。
它有以下主要属性
compilation.modules 可以访问所有模块打包的每一个文件都是一个模块。compilation.chunks chunk 即是多个 modules 组成而来的一个代码块。入口文件引入的资源组成一个 chunk通过代码分割的模块又是另外的 chunk。compilation.assets 可以访问本次打包生成所有文件的结果。compilation.hooks 可以注册 tapable 的不同种类 Hook用于在 compilation 编译模块阶段进行逻辑添加以及修改。
compilation hooks 文档
开发一个插件
最简单的插件
plugins/test-plugin.js
class TestPlugin {constructor() {console.log(TestPlugin constructor());}// 1. webpack读取配置时new TestPlugin() 会执行插件 constructor 方法// 2. webpack创建 compiler 对象// 3. 遍历所有插件调用插件的 apply 方法apply(compiler) {console.log(TestPlugin apply());}
}module.exports TestPlugin;
注册 hook
class TestPlugin {constructor() {console.log(TestPlugin constructor());}// 1. webpack读取配置时new TestPlugin() 会执行插件 constructor 方法// 2. webpack创建 compiler 对象// 3. 遍历所有插件调用插件的 apply 方法apply(compiler) {console.log(TestPlugin apply());// 从文档可知, compile hook 是 SyncHook, 也就是同步钩子, 只能用tap注册compiler.hooks.compile.tap(TestPlugin, (compilationParams) {console.log(compiler.compile());});// 从文档可知, make 是 AsyncParallelHook, 也就是异步并行钩子, 特点就是异步任务同时执行// 可以使用 tap、tapAsync、tapPromise 注册。// 如果使用tap注册的话进行异步操作是不会等待异步操作执行完成的。compiler.hooks.make.tap(TestPlugin, (compilation) {setTimeout(() {console.log(compiler.make() 111);}, 2000);});// 使用tapAsync、tapPromise注册进行异步操作会等异步操作做完再继续往下执行compiler.hooks.make.tapAsync(TestPlugin, (compilation, callback) {setTimeout(() {console.log(compiler.make() 222);// 必须调用callback();}, 1000);});compiler.hooks.make.tapPromise(TestPlugin, (compilation) {console.log(compiler.make() 333);// 必须返回promisereturn new Promise((resolve) {resolve();});});// 从文档可知, emit 是 AsyncSeriesHook, 也就是异步串行钩子特点就是异步任务顺序执行compiler.hooks.emit.tapAsync(TestPlugin, (compilation, callback) {setTimeout(() {console.log(compiler.emit() 111);callback();}, 3000);});compiler.hooks.emit.tapAsync(TestPlugin, (compilation, callback) {setTimeout(() {console.log(compiler.emit() 222);callback();}, 2000);});compiler.hooks.emit.tapAsync(TestPlugin, (compilation, callback) {setTimeout(() {console.log(compiler.emit() 333);callback();}, 1000);});}
}module.exports TestPlugin;
启动调试
通过调试查看 compiler 和 compilation 对象数据情况。
package.json 配置指令
{name: source,version: 1.0.0,scripts: {debug: node --inspect-brk ./node_modules/webpack-cli/bin/cli.js},keywords: [],author: xiongjian,license: ISC,devDependencies: {babel/core: ^7.17.10,babel/preset-env: ^7.17.10,css-loader: ^6.7.1,loader-utils: ^3.2.0,webpack: ^5.72.0,webpack-cli: ^4.9.2}
}
运行指令
npm run debug此时控制台输出以下内容
PS C:\Users\86176\Desktop\source npm run debug source1.0.0 debugnode --inspect-brk ./node_modules/webpack-cli/bin/cli.jsDebugger listening on ws://127.0.0.1:9229/629ea097-7b52-4011-93a7-02f83c75c797
For help, see: https://nodejs.org/en/docs/inspecto
打开 Chrome 浏览器F12 打开浏览器调试控制台。
此时控制台会显示一个绿色的图标
调试控制台
点击绿色的图标进入调试模式。
在需要调试代码处用 debugger 打断点代码就会停止运行从而调试查看数据情况。
BannerWebpackPlugin
作用给打包输出文件添加注释。
开发思路:
需要打包输出前添加注释需要使用 compiler.hooks.emit 钩子, 它是打包输出前触发。
如何获取打包输出的资源compilation.assets 可以获取所有即将输出的资源文件。
实现
// plugins/banner-webpack-plugin.js
class BannerWebpackPlugin {constructor(options {}) {this.options options;}apply(compiler) {// 需要处理文件const extensions [js, css];// emit是异步串行钩子compiler.hooks.emit.tapAsync(BannerWebpackPlugin, (compilation, callback) {// compilation.assets包含所有即将输出的资源// 通过过滤只保留需要处理的文件const assetPaths Object.keys(compilation.assets).filter((path) {const splitted path.split(.);return extensions.includes(splitted[splitted.length - 1]);});assetPaths.forEach((assetPath) {const asset compilation.assets[assetPath];const source /*
* Author: ${this.options.author}
*/\n${asset.source()};// 覆盖资源compilation.assets[assetPath] {// 资源内容source() {return source;},// 资源大小size() {return source.length;},};});callback();});}
}module.exports BannerWebpackPlugin;
CleanWebpackPlugin
作用在 webpack 打包输出前将上次打包内容清空。
开发思路
如何在打包输出前执行需要使用 compiler.hooks.emit 钩子, 它是打包输出前触发。如何清空上次打包内容
获取打包输出目录通过 compiler 对象。通过文件操作清空内容通过 compiler.outputFileSystem 操作文件。
实现
// plugins/clean-webpack-plugin.js
class CleanWebpackPlugin {apply(compiler) {// 获取操作文件的对象const fs compiler.outputFileSystem;// emit是异步串行钩子compiler.hooks.emit.tapAsync(CleanWebpackPlugin, (compilation, callback) {// 获取输出文件目录const outputPath compiler.options.output.path;// 删除目录所有文件const err this.removeFiles(fs, outputPath);// 执行成功err为undefined执行失败err就是错误原因callback(err);});}removeFiles(fs, path) {try {// 读取当前目录下所有文件const files fs.readdirSync(path);// 遍历文件删除files.forEach((file) {// 获取文件完整路径const filePath ${path}/${file};// 分析文件const fileStat fs.statSync(filePath);// 判断是否是文件夹if (fileStat.isDirectory()) {// 是文件夹需要递归遍历删除下面所有文件this.removeFiles(fs, filePath);} else {// 不是文件夹就是文件直接删除fs.unlinkSync(filePath);}});// 最后删除当前目录fs.rmdirSync(path);} catch (e) {// 将产生的错误返回出去return e;}}
}module.exports CleanWebpackPlugin;
AnalyzeWebpackPlugin
作用分析 webpack 打包资源大小并输出分析文件。
开发思路:
在哪做? compiler.hooks.emit, 它是在打包输出前触发我们需要分析资源大小同时添加上分析后的 md 文件。
实现
// plugins/analyze-webpack-plugin.js
class AnalyzeWebpackPlugin {apply(compiler) {// emit是异步串行钩子compiler.hooks.emit.tap(AnalyzeWebpackPlugin, (compilation) {// Object.entries将对象变成二维数组。二维数组中第一项值是key第二项值是valueconst assets Object.entries(compilation.assets);let source # 分析打包资源大小 \n| 名称 | 大小 |\n| --- | --- |;assets.forEach(([filename, file]) {source \n| ${filename} | ${file.size()} |;});// 添加资源compilation.assets[analyze.md] {source() {return source;},size() {return source.length;},};});}
}module.exports AnalyzeWebpackPlugin;
InlineChunkWebpackPlugin
作用webpack 打包生成的 runtime 文件太小了额外发送请求性能不好所以需要将其内联到 js 中从而减少请求数量。
开发思路:
1、我们需要借助 html-webpack-plugin 来实现
在 html-webpack-plugin 输出 index.html 前将内联 runtime 注入进去删除多余的 runtime 文件
2、如何操作 html-webpack-plugin官方文档
实现
// plugins/inline-chunk-webpack-plugin.js
const HtmlWebpackPlugin require(safe-require)(html-webpack-plugin);class InlineChunkWebpackPlugin {constructor(tests) {this.tests tests;}apply(compiler) {compiler.hooks.compilation.tap(InlineChunkWebpackPlugin, (compilation) {const hooks HtmlWebpackPlugin.getHooks(compilation);hooks.alterAssetTagGroups.tap(InlineChunkWebpackPlugin, (assets) {assets.headTags this.getInlineTag(assets.headTags, compilation.assets);assets.bodyTags this.getInlineTag(assets.bodyTags, compilation.assets);});hooks.afterEmit.tap(InlineChunkHtmlPlugin, () {Object.keys(compilation.assets).forEach((assetName) {if (this.tests.some((test) assetName.match(test))) {delete compilation.assets[assetName];}});});});}getInlineTag(tags, assets) {return tags.map((tag) {if (tag.tagName ! script) return tag;const scriptName tag.attributes.src;if (!this.tests.some((test) scriptName.match(test))) return tag;return { tagName: script, innerHTML: assets[scriptName].source(), closeTag: true };});}
}module.exports InlineChunkWebpackPlugin;