来源:未知 时间:2023-10-13 13:53 作者:小飞侠 阅读:次
[导读] Webpack通过Loader完成模块的转换工作,让“一切皆模块”成为可能。Plugin机制则让其更加灵活,可以在Webpack生命周期中调用钩子完成各种任务,包括修改输出资源、输出目录等等。 今天...
Webpack通过Loader完成模块的转换工作,让“一切皆模块”成为可能。Plugin机制则让其更加灵活,可以在Webpack生命周期中调用钩子完成各种任务,包括修改输出资源、输出目录等等。 今天我们一起来学习如何编写Webpack插件。 构建流程在编写插件之前,还需要了解一下Webpack的构建流程,以便在合适的时机插入合适的插件逻辑。Webpack的基本构建流程如下:
插件示例一个典型的Webpack插件代码如下: // 插件代码 class MyWebpackPlugin { constructor(options) { } apply(compiler) { // 在emit阶段插入钩子函数 compiler.hooks.emit.tap('MyWebpackPlugin', (compilation) => {}); } } module.exports = MyWebpackPlugin; 接下来需要在webpack.config.js中引入这个插件。 module.exports = { plugins:[ // 传入插件实例 new MyWebpackPlugin({ param:'paramValue' }), ] }; Webpack在启动时会实例化插件对象,在初始化compiler对象之后会调用插件实例的apply方法,传入compiler对象,插件实例在apply方法中会注册感兴趣的钩子,Webpack在执行过程中会根据构建阶段回调相应的钩子。 Compiler && Compilation对象在编写Webpack插件过程中,最常用也是最主要的两个对象就是Webpack提供的Compiler和Compilation,Plugin通过访问Compiler和Compilation对象来完成工作。
常见钩子Webpack会根据执行流程来回调对应的钩子,下面我们来看看都有哪些常见钩子,这些钩子支持的tap操作是什么。
TapableTapable是Webpack的一个核心工具,Webpack中许多对象扩展自Tapable类。Tapable类暴露了tap、tapAsync和tapPromise方法,可以根据钩子的同步/异步方式来选择一个函数注入逻辑。
taptap是一个同步钩子,同步钩子在使用时不可以包含异步调用,因为函数返回时异步逻辑有可能未执行完毕导致问题。 下面一个在compile阶段插入同步钩子的示例。 compiler.hooks.compile.tap('MyWebpackPlugin', params => { console.log('我是同步钩子') }); tapAsynctapAsync是一个异步钩子,我们可以通过callback告知Webpack异步逻辑执行完毕。 下面是一个在emit阶段的示例,在1秒后打印文件列表。 compiler.hooks.emit.tapAsync('MyWebpackPlugin', (compilation, callback) => { setTimeout(()=>{ console.log('文件列表', Object.keys(compilation.assets).join(',')); callback(); }, 1000); }); tapPromisetapPromise也是也是异步钩子,和tapAsync的区别在于tapPromise是通过返回Promise来告知Webpack异步逻辑执行完毕。 下面是一个将生成结果上传到CDN的示例。 compiler.hooks.afterEmit.tapPromise('MyWebpackPlugin', (compilation) => { return new Promise((resolve, reject) => { const filelist = Object.keys(compilation.assets); uploadToCDN(filelist, (err) => { if(err) { reject(err); return; } resolve(); }); }); }); apply方法中插入钩子的一般形式如下: compileer.hooks.阶段.tap函数('插件名称', (阶段回调参数) => { }); 常用API读取输出资源、模块及依赖在emit阶段,我们可以读取最终需要输出的资源、chunk、模块和对应的依赖,如果有需要还可以更改输出资源。 apply(compiler) { compiler.hooks.emit.tapAsync('MyWebpackPlugin', (compilation, callback) => { // compilation.chunks存放了代码块列表 compilation.chunks.forEach(chunk => { // chunk包含多个模块,通过chunk.modulesIterable可以遍历模块列表 for(const module of chunk.modulesIterable) { // module包含多个依赖,通过module.dependencies进行遍历 module.dependencies.forEach(dependency => { console.log(dependency); }); } }); callback(); }); } 修改输出资源通过操作compilation.assets对象,我们可以添加、删除、更改最终输出的资源。 apply(compiler) { compiler.hooks.emit.tapAsync('MyWebpackPlugin', (compilation) => { // 修改或添加资源 compilation.assets['main.js'] = { source() { return 'modified content'; }, size() { return this.source().length; } }; // 删除资源 delete compilation.assets['main.js']; }); } assets对象需要定义source和size方法,source方法返回资源的内容,支持字符串和Node.js的Buffer,size返回文件的大小字节数。 插件编写实例接下来我们开始编写自定义插件,所有插件使用的示例项目如下(需要安装webpack和webpack-cli): |----src |----main.js |----plugins |----my-webpack-plugin.js |----package.json |----webpack.config.js 相关文件的内容如下: // src/main.jsconsole.log('Hello World'); // package.json{ "scripts":{ "build":"webpack" }} const path = require('path'); const MyWebpackPlugin = require('my-webpack-plugin'); // webpack.config.js module.exports = { entry:'./src/main', output:{ path: path.resolve(__dirname, 'build'), filename:'[name].js', }, plugins:[ new MyWebpackPlugin() ] }; 生成清单文件通过在emit阶段操作compilation.assets实现。 class MyWebpackPlugin { apply(compiler) { compiler.hooks.emit.tapAsync('MyWebpackPlugin', (compilation, callback) => { const manifest = {}; for (const name of Object.keys(compilation.assets)) { manifest[name] = compilation.assets[name].size(); // 将生成文件的文件名和大小写入manifest对象 } compilation.assets['manifest.json'] = { source() { return JSON.stringify(manifest); }, size() { return this.source().length; } }; callback(); }); } } module.exports = MyWebpackPlugin; 构建完成后会在build目录添加manifest.json,内容如下: {"main.js":956} 构建结果上传到七牛在实际开发中,资源文件构建完成后一般会同步到CDN,最终前端界面使用的是CDN服务器上的静态资源。 下面我们编写一个Webpack插件,文件构建完成后上传的七牛CDN。 我们的插件依赖qiniu,因此需要额外安装qiniu模块 npm install qiniu --save-dev 七牛的Node.js SDK文档地址如下: https://developer.qiniu.com/kodo/sdk/1289/nodejs 开始编写插件代码: const qiniu = require('qiniu'); const path = require('path'); class MyWebpackPlugin { // 七牛SDK mac对象 mac = null; constructor(options) { // 读取传入选项 this.options = options || {}; // 检查选项中的参数 this.checkQiniuConfig(); // 初始化七牛mac对象 this.mac = new qiniu.auth.digest.Mac( this.options.qiniu.accessKey, this.options.qiniu.secretKey ); } checkQiniuConfig() { // 配置未传qiniu,读取环境变量中的配置 if (!this.options.qiniu) { this.options.qiniu = { accessKey: process.env.QINIU_ACCESS_KEY, secretKey: process.env.QINIU_SECRET_KEY, bucket: process.env.QINIU_BUCKET, keyPrefix: process.env.QINIU_KEY_PREFIX || '' }; } const qiniu = this.options.qiniu; if (!qiniu.accessKey || !qiniu.secretKey || !qiniu.bucket) { throw new Error('invalid qiniu config'); } } apply(compiler) { compiler.hooks.afterEmit.tapPromise('MyWebpackPlugin', (compilation) => { return new Promise((resolve, reject) => { // 总上传数量 const uploadCount = Object.keys(compilation.assets).length; // 已上传数量 let currentUploadedCount = 0; // 七牛SDK相关参数 const putPolicy = new qiniu.rs.PutPolicy({ scope: this.options.qiniu.bucket }); const uploadToken = putPolicy.uploadToken(this.mac); const config = new qiniu.conf.Config(); config.zone = qiniu.zone.Zone_z1; const formUploader = new qiniu.form_up.FormUploader() const putExtra = new qiniu.form_up.PutExtra(); // 因为是批量上传,需要在最后将错误对象回调 let globalError = null; // 遍历编译资源文件 for (const filename of Object.keys(compilation.assets)) { // 开始上传 formUploader.putFile( uploadToken, this.options.qiniu.keyPrefix + filename, path.resolve(compilation.outputOptions.path, filename), putExtra, (err) => { console.log(`uploade ${filename} result: ${err ? `Error:${err.message}` : 'Success'}`) currentUploadedCount++; if (err) { globalError = err; } if (currentUploadedCount === uploadCount) { globalError ? reject(globalError) : resolve(); } }); } }) }); } } module.exports = MyWebpackPlugin; Webpack中需要传递给该插件传递相关配置: module.exports = { entry: './src/index', target: 'node', output: { path: path.resolve(__dirname, 'build'), filename: '[name].js', publicPath: 'CDN域名' }, plugins: [ new CleanWebpackPlugin(), new QiniuWebpackPlugin({ qiniu: { accessKey: '七牛AccessKey', secretKey: '七牛SecretKey', bucket: 'static', keyPrefix: 'webpack-inaction/demo1/' } }) ] }; 编译完成后资源会自动上传到七牛CDN,这样前端只用交付index.html即可。 |
自学PHP网专注网站建设学习,PHP程序学习,平面设计学习,以及操作系统学习
京ICP备14009008号-1@版权所有www.zixuephp.com
网站声明:本站所有视频,教程都由网友上传,站长收集和分享给大家学习使用,如由牵扯版权问题请联系站长邮箱904561283@qq.com