主要记录一下自己手写的Loader和plugin,理论介绍去官网自行学习😊
自定义loader
loader 是导出为一个函数的 node 模块。该函数在 loader 转换资源的时候调用。给定的函数将调用 Loader API,并通过 this 上下文访问, 更多的详情介绍见官网:loader
类型
见官网(链接同上)
用法准则
见官网(链接同上)
自定义步骤;
- 创建一个
loaders文件夹 - 在loaders文件夹中创建一个自定义的loader文件
- 在
webpack.config.js中配置loader- 添加配置
resolveLoader:{modules:[path.resolve(__dirname,'./src/loaders')]}, 详细看webpack.config.js module.loaders中添加自定义loader
- 添加配置
- 打包测试,查看
bundle.js中已经有了变化
手写过loader吗
主要就是导出一个函数模块,他的参数是一个源代码字符串或者是上一个loader的处理结果,因为loader支持链式调用, 返回的是处理后的代码字符串,或者是可以被下一个loader继续处理的结果。在处理过程中,可以使用 this 上下文访问 Loader API。也可以借助 loader-utils 包来进行一些工具函数的调用。
要使用自己本地写好的loader,需要在webpack.config.js中配置调用方式,
- 单个loader的调用 直接用
path.resolve(__dirname,'./src/loaders/console-loader.js') - 多个loader的调用 要配置
resolveLoader:{modules:[path.resolve(__dirname,'./src/loaders')]}
一个loader简单实战:实现一个添加注释和去除打印的loader
创建文件如下:/src/loaders/
编写loader
/**
* annotation-loader.js
* 注解loader
*/
module.exports = function (source) {
this.cacheable(); //标记当前loader可以缓存
const loaderContext = this; //获取当前loader的上下文
const callback = loaderContext.async(); //获取当前loader的回调函数
const options = loaderContext.getOptions(); //获取当前loader的配置
callback(null,addAnnotation(source,options.sign,options.author)); //返回注解后的代码
return;
}
function addAnnotation(source, sign, author) {
return `
/**
* ${sign}
* @author: ${author}
* @date: ${new Date().toLocaleString()}
*/
${source}`
}
/**
* console-loader
* 删除js中的console.log(comment)
*/
module.exports = function (source) {
this.cacheable();
const loaderContext = this;
const callback = loaderContext.async();
callback(null, removeConsole(source));
return;
}
function removeConsole(source) {
return source.replace(/console\.log\((.*)\)/g, '');
}
webpack.config.js配置本地loader
...
resolveLoader:{
//自定义loader的路径,先检索node_modules中的loader,再检索本地的loader
modules:[
'node_modules',
path.resolve(__dirname,'./src/loaders')
]
},
module: {
rules:[
{
test: /\.js$/,
use:[
'console-loader',
{
loader: 'annotation-loader',
options:{
sign:'这是通过自定义loader添加的一条注释',
author:'faaarii'
}
},
]
},
]
}
...
运行测试
webpack --config ./webpack.config.js 或者你自己写好的npm script
自定义plugin
插件导出的是一个类,包含一个apply方法,在装载的时候被调用,传入一个compiler对象(全局唯一),内部处理是用调用hooks
Compiler 和 Compilation 是整个编写插件的过程中的重!中!之!重
compiler对象可以理解为一个和 webpack 环境整体绑定的一个对象,它包含了所有的环境配置,包括 options,loader 和 plugin,当 webpack 启动时,这个对象会被实例化,并且他是全局唯一的,上面我们说到的apply方法传入的参数就是它。compilation在每次构建资源的过程中都会被创建出来,一个 compilation 对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息
基本的架构
module.exports = class customPlugin {
apply(compiler) {
compiler.hooks.<hookName>.tap/tapAsync/tapPromise(<pluginName>, <callback>);
}
}
一个plugin简单实战:构建后生成一个资源统计文件,记录每个文件构建后的大小
创建本地plugins文件夹,并创建createAssetsPlugin.js文件
编写插件
module.exports = class createAssetsPlugin {
constructor(options) {
this.options = options; //配置
}
apply(compiler) {
const { webpack } = compiler; //webpack实例
const { Compilation } = webpack; //webpack编译类
const { RawSource } = webpack.sources; //在 compilation 中表示资源的源码
//指定一个挂载到 compilation 的钩子,回调函数的参数为 compilation 。
compiler.hooks.compilation.tap('createAssetsPlugin', compilation => {
//通过 compilation 对象绑定各种钩子,绑定到资源处理流水线(assets processing pipeline)
compilation.hooks.processAssets.tap(
{
name: 'createAssetsPlugin',
// 用某个靠后的资源处理阶段,
// 确保所有资源已被插件添加到 compilation
stage: Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE,
},
assets => {
// "assets" 是一个包含 compilation 中所有资源(assets)的对象。
// 该对象的键是资源的路径,
// 值是文件的源码
let resOutput = `构建时间: ${new Date().toLocaleString()}\n`;
resOutput += `| 文件名 | 文件大小 |\n| --------- | --------- |\n`;
Object.entries(assets).forEach(([pathname, source]) => {
resOutput += `| ${pathname} | ${source.size()} bytes |\n`;
});
//向 compilation 添加新的资源
compilation.emitAsset('assets.md', new RawSource(resOutput));
}
);
});
}
}
在webpack.config.js中配置本地插件
//自定义插件
const createAssetsPlugin = require('./src/plugins/createAssetsPlugin')
...
plugins: [
new createAssetsPlugin(),
],
...
运行测试
webpack --config ./webpack.config.js 或者你自己写好的npm script
能看到/dist文件下已经生成assets.md
over🎉