webpack-手写loader和plugin

316 阅读2分钟

主要记录一下自己手写的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/

image.png

编写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

image.png

自定义plugin

插件导出的是一个类,包含一个apply方法,在装载的时候被调用,传入一个compiler对象(全局唯一),内部处理是用调用hooks

CompilerCompilation 是整个编写插件的过程中的重!中!之!重

  • 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

image.png

image.png over🎉