前端工程化:webpack 的plugin

1、loader和plugin的区别

loader的应用场景是“转换(transform)”,简单的来说就是将内容从一种格式转换为另一种,其中包含es6->es5、将资源从bundle中解耦出来、scss|less等高级语言的css化,文件的加载等。

plugin的应用场景是针对webpack各个事件的钩子处理函数,简单的来说就是在各个时间点做一些“扩展”,比如:报告打印、import组件的次数记录、模块加载时间分析、压缩代码、模块大小分析等。

由此可见loader侧重于文件的转换和转化,而plugin侧重全局的把控和扩展。

2、loader例子:

// loader-utils是专门用于自定义loader时的一些工具函数

module.exports = function(source) {
    const upperCaseContent = source.toUpperCase();
    const content = `module.exports = ${JSON.stringify(upperCaseContent)}`;
    // console.log("########content:",content);
    return content;
};

这是一个典型的loader转化场景,将小写英文转化为大写。结果如下:

module.exports = "NO ONE’S BORN BEING GOOD AT ALL THINGS. YOU BECOME GOOD AT THINGS THROUGH HARD WORK. YOU’RE NOT A VARSITY ATHLETE THE FIRST TIME YOU PLAY A NEW SPORT. YOU DON’T HIT EVERY NOTE THE FIRST TIME YOU SING A SONG.YOU’VE GOT TO PRACTICE. THE SAME PRINCIPLE APPLIES TO YOUR SCHOOLWORK. YOU MIGHT HAVE TO DO A MATH PROBLEM A FEW TIMES BEFORE YOU GET IT RIGHT. YOU MIGHT HAVE TO READ SOMETHING A FEW TIMES BEFORE YOU UNDERSTAND IT.YOU DEFINITELY HAVE TO DO A FEW DRAFTS OF A PAPER BEFORE IT’S GOOD ENOUGH TO HAND IN."

加入module.exports是为了在将文本嵌入bundle.js后能以模块的形式读取它,当然一般都是配合file-loader将文件提取出来:

  module: {
    rules: [
        { 
          test: /\.txt$/, 
          use: [
            {
              loader: 'file-loader',
              options: {
                  name: '[name].[hash].[ext]', // 配置文件的输出名称
              },
          },
            
            { 
              // 本地引用loader
              loader: path.resolve('./src/my-loader'),
              options: {
                  // 通过配置传入words来替换NAME为wei
                  words: 'wei'
              }
          }]
      }
    ],
  },

在 Webpack 中,loader 的顺序非常重要,因为它们的执行顺序是从 右到左 或者 下到上 的,这意味着最后一个定义的 loader 会最先执行。

3、plugin例子:

class ComponentReferenceCounterPlugin {
    constructor(options) {
        this.options = options;
        this.total = { len: 0, components: {} };
    }

    apply(compiler) {
        const self = this;

        const parser = (factory) => {
            factory.hooks.parser.for('javascript/auto').tap('count-webpack-plugin', (parser) => {
                parser.hooks.importSpecifier.tap('count-webpack-plugin', (statement, source, exportName, identifierName) => {
                    console.log("@@@@@@@@@@@@@@@@@@@@@",source,identifierName,exportName);
                    if (source.includes(self.options.pathname)) {
                        self.total.len += 1;
                        const key = identifierName;
                        self.total.components[key] = self.total.components[key]? self.total.components[key] + 1 : 1;
                    }
                });
            });
        };

        compiler.hooks.normalModuleFactory.tap('count-webpack-plugin', parser);

        compiler.hooks.done.tap('count-webpack-plugin-done', () => {
            console.log('Component reference count:', self.total);
        });
    }
}

module.exports = ComponentReferenceCounterPlugin;

基本上这个plugin做了:
【1】factory.hooks.parser.for('javascript/auto').tap(...): 这里使用了 Webpack 的 parser hook 来监听 JavaScript 文件的解析过程。javascript/auto 表示监听所有 JavaScript 类型的模块。
【2】parser.hooks.importSpecifier.tap(...): 这个 hook 在解析 import 语句时触发,可以获取到导入的模块路径(source)、导出的名称(exportName)以及标识符名称(identifierName)。
【3】compiler.hooks.normalModuleFactory.tap(...): 这个 hook 用于注册 parser 函数到 Webpack 的 normalModuleFactory 中。normalModuleFactory 负责生成模块实例,因此在这个阶段挂载解析器来统计模块引用是合适的。

得到结果是:Component reference count: { len: 1, components: { child: 1 } }

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部