资源读取插件实现

自定义 webpack 插件前,先了解一下如何实现同步插件和异步插件。

1. 基本环境准备

创建一个名为 webpack_plugins 的文件夹。

mkdir webpack-plugins

进入此文件夹,创建 package.json 文件。

cd webpack-plugins
npm init

安装 webpack 及 webpack-cli 工具。

npm i webpack webpack-cli --save-dev

创建 src 文件夹及 src/index.js 文件,编写测试脚本。

mkdir src && cd src && touch index.js
/** * @file 入口文件 * @module src/index.js * @version 0.1.0 * @author yueluo <yueluo.yang@qq.com> * @time 2020-07-04 */ console.log('hello world.');

根目录下创建 webpack.config.js 文件,并编写配置文件。

touch webpack.config.js
/** * @file webapck配置文件 * @module webpack.config.js * @version 0.1.0 * @author yueluo <yueluo.yang@qq.com> * @time 2020-07-04 */ /** * @requires node_modules/path node内置模块 */ const path = require('path'); // 导出webpack配置 module.exports = { mode: 'development', entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist') } }

运行 npx webpack 命令,出现 dist 目录 及 bundle.js 文件,说明配置完成。

plugins1.png

2. 同步插件实现

(1)创建plugins文件夹

根目录下创建 plugins 文件夹,用于存放自定义的 webpack 插件。

mkdir plugins

(2)创建DonePlugin.js文件

在 plugins 文件夹中创建 DonePlugin.js 文件,用于编写同步插件。

cd plugins && touch DonePlugin.js

(3)编写同步插件

webpack 插件其实就是一个类,类中需要实现 apply 方法,webpack 运行时会调用该方法并为该方法注入 compiler 对象。

Compiler 对象包含了当前运行Webpack的配置,包括entry、output、loaders等配置,这个对象在启动Webpack时被实例化,而且是全局唯一的。Plugin可以通过该对象获取到Webpack的配置信息进行处理。

/** * @file 同步插件 * @module plugins/DonePlugin.js * @version 0.1.0 * @author yueluo <yueluo.yang@qq.com> * @time 2020-07-04 */ /** * @class DonePlugin * @classdesc 同步插件 */ class DonePlugin { /** * @description webpack插件运行使用的方法 * @param {object} compiler - webpack运行时自动注入 * @return {string} */ apply (compiler) { compiler.hooks.done.tap('DonePlugin', (status) => { console.log('同步插件编译完成'); }); } } // 导出同步插件 module.exports = DonePlugin;

在上面的代码中,可以看到,我们可以使用 compiler 对象中的 tap 方法注册插件名称。

(4)使用同步插件

webpack.config.js 文件中引入并使用同步插件。

/** * @file webapck配置文件 * @module webpack.config.js * @version 0.1.0 * @author yueluo <yueluo.yang@qq.com> * @time 2020-07-04 */ /** * @requires node_modules/path node内置模块 * @requires plugins/DonePlugin 同步插件 */ const path = require('path'), DonePlugin = require('./plugins/DonePlugin'); // 导出webpack配置 module.exports = { mode: 'development', entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist') }, plugins: [ new DonePlugin() ] }

好了,下面运行 npx webpack 命令打包测试。

plugins2.png

如上图所示,出现 “同步插件编译完成” 字样,说明同步插件编写完成。

3. 异步插件实现

(1)创建AsyncPlugin.js文件

在 plugins 文件夹中创建 AsyncPlugin.js 文件,用于编写异步插件。

cd plugins && touch AsyncPlugin.js

(2)编写异步插件

异步插件有两种实现方式,一种是回调函数的方式,一种是 promise 的方式。

回调函数的方式。

/** * @file 异步插件 * @module plugins/AsyncPlugin.js * @version 0.1.0 * @author yueluo <yueluo.yang@qq.com> * @time 2020-07-04 */ /** * @class AsyncPlugin * @classdesc 异步插件 */ class AsyncPlugin { /** * @description webpack插件运行使用的方法 * @param {object} compiler - webpack运行时自动注入 * @return {string} */ apply (compiler) { compiler.hooks.emit.tapAsync('AsyncPlugin', (compilation, cb) => { setTimeout(() => { console.log('emit done.'); cb(); }, 1000) }); } } // 导出异步插件 module.exports = AsyncPlugin;

promise 的方式。

/** * @file 异步插件 * @module plugins/AsyncPlugin.js * @version 0.1.0 * @author yueluo <yueluo.yang@qq.com> * @time 2020-07-04 */ /** * @class AsyncPlugin * @classdesc 异步插件 */ class AsyncPlugin { /** * @description webpack插件运行使用的方法 * @param {object} compiler - webpack运行时自动注入 * @return {string} */ apply (compiler) { compiler.hooks.emit.tapPromise('AsyncPlugin', (compilation) => { return new Promise((resolve, reject) => { setTimeout(() => { console.log('emit done.'); resolve(); }, 1000); }); }); } } // 导出异步插件 module.exports = AsyncPlugin;

两种方式都可以实现功能,个人推荐使用 promise 的方式。

(3)使用异步插件

webpack.config.js 文件中引入并使用异步插件。

/** * @file webapck配置文件 * @module webpack.config.js * @version 0.1.0 * @author yueluo <yueluo.yang@qq.com> * @time 2020-07-04 */ /** * @requires node_modules/path node内置模块 * @requires plugins/DonePlugin 同步插件 * @requires plugins/AsyncPlugin 异步插件 */ const path = require('path'), DonePlugin = require('./plugins/DonePlugin'), AsyncPlugin = require('./plugins/AsyncPlugin'); // 导出webpack配置 module.exports = { mode: 'development', entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist') }, plugins: [ new DonePlugin(), new AsyncPlugin() ] }

编写完成后,使用 npx webpack 命令进行测试。

plugins3.png

出现 “emit done.” 字样,说明异步插件编写完成。

5. 资源读取插件实现

插件主要的功能是记录打包后 dist 目录中文件的大小和名称。

(1)编写测试文件

src 目录下创建 index.html 和 index.css 文件。

cd src && touch index.html && index.css

在 index.html 和 index.css 中编写测试代码。

/* 入口文件样式 */ body { background-color: orange; }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>yuelou</title> </head> <body> </body> </html>

入口文件中引入 index.css 文件。

/** * @file 入口文件 * @module src/index.js * @version 0.1.0 * @author yueluo <yueluo.yang@qq.com> * @time 2020-07-04 */ /** * @requires src/index.css 入口文件的样式 */ require('./index.css'); console.log('hello world.');

plugins 文件中创建 AssetPlugin.js 文件。

cd plugins && touch AssetPlugin.js

(2) 编写webpack配置文件

安装 html-webpack-plugin 插件,用于生成 html 文件。

npm i html-webpack-plugin --save-dev

安装 css-loader 和 mini-css-extract-plugin,用于处理并生成 css 文件。

npm i css-loader mini-css-extract-plugin --save-dev

引入并使用 webpack 插件和自定义的 AssetPlugin 插件。

/** * @file webapck配置文件 * @module webpack.config.js * @version 0.1.0 * @author yueluo <yueluo.yang@qq.com> * @time 2020-07-04 */ /** * @requires node_modules/path node内置模块 * @requires plugins/DonePlugin 同步插件 * @requires plugins/AsyncPlugin 异步插件 * @requires plugins/AssetPlugin 资源读取插件 * @requires node_modules/HtmlWebpackPlugin html文件生成插件 * @requires node_modules/MiniCssExtractPlugin CSS文件生成插件 */ const path = require('path'), DonePlugin = require('./plugins/DonePlugin'), AsyncPlugin = require('./plugins/AsyncPlugin'), AssetPlugin = require('./plugins/AssetPlugin'), HtmlWebpackPlugin = require('html-webpack-plugin'), MiniCssExtractPlugin = require('mini-css-extract-plugin'); // 导出webpack配置 module.exports = { mode: 'development', entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist') }, module: { rules: [ { test: /\.css$/, use: [ MiniCssExtractPlugin.loader, 'css-loader' ] } ] }, plugins: [ new HtmlWebpackPlugin({ template: './src/index.html' }), new MiniCssExtractPlugin(), new DonePlugin(), new AsyncPlugin(), new AssetPlugin({ filename: 'assets-list.md' }) ] }

自定义的 AssetPlugin 插件可以配置生成的文件名称。

(3)编写AssetPlugin插件

在 webpack 的 emit 阶段,我们可以使用同步钩子 tap 函数注册插件名称,可以在 compilation 对象中获取到需要处理的文件资源。

Compilation对象可以理解编译对象,包含了模块、依赖、文件等信息。在开发模式下运行Webpack时,每修改一次文件都会产生一个新的Compilation对象,Plugin可以访问到本次编译过程中的模块、依赖、文件内容等信息。

/** * @file 资源读取插件 * @module plugins/AssetPlugin.js * @version 0.1.0 * @author yueluo <yueluo.yang@qq.com> * @time 2020-07-04 */ /** * @class AssetPlugin * @classdesc 资源读取插件 */ class AssetPlugin { /** * @constructor 构造函数 * @param {string} filename - 文件名称 * @return {void} */ constructor ({ filename }) { this.filename = filename; } /** * @description webpack插件运行使用的方法 * @param {object} compiler - webpack运行时自动注入 * @return {string} */ apply (compiler) { compiler.hooks.emit.tap('AssetPlugin', (compilation) => { const assets = compilation.assets; let content = `## 文件名 大小\r\n`; // 创建文件内容 Object.entries(assets).forEach(([filename, stat]) => { content += `- ${filename} ${stat.size()}\r\n`; }); assets[this.filename] = { source () { return content; }, size () { return content.length; } } }); } } // 导出资源读取插件 module.exports = AssetPlugin;

插件编写完成后,运行 npx webpack 命令进行打包测试。

plugins4.png

plugins5.png

如上图所示,如果 dist 目录下生成 assets-list.md 文件,并且正常记录打包后的资源文件信息,说明资源读取插件编写完成。

6. 总结

本篇文章介绍了如何实现自己的 webpack 插件,希望能对大家有所帮助。