webpack4 plugin 的原理和实现部分plugin

黄兴业
2023-12-01

webpack4 plugin 的原理和实现部分plugin

1.Plugin

  • plugin 本质上就是一个类,有一个 apply 方法,接受一个 compiler 的对象, 我们会在 compiler 对象上钩子挂载一些监听函数,当 compiler 对象上这些钩子触发的时候,就会调用这些函数
  • 插件向第三方开发者提供了 webpack 引擎中完整的能力,使用阶段式的构建回调,开发者可以引入它们自己的行为到 webpack 构建的流程中,创建插件比创建 loader 更加高级,因为你将需要 理解一些 webpack 底层的内部特性来做相应的钩子。
1.1 为什么需要一个插件
  • webpack 基础配置无法满足需求
  • 插件几乎能够任意更改 webpack 编译结果
  • webpack 内部也是通过大量的内部插件实现的
1.2 可以加载插件的常用对象
  • 对象 钩子
  • compiler run,compile,compilation,make,emit,done
  • Compilation buildModule,normalModuleLoader,succeedModule,finishModules,seal,optimize,after-seal
  • Module Factory beforeResolver,afterResolver,module,parser
  • Module
  • Parser program,statement,call,expression
  • Template hash,bootstrap,localVars,render

2.创建插件

webpack 插件由以下组成
  • 一个 JavaScript 命名函数。
  • 指定一个绑定到 webpack 内部实例的特定数据。
  • 处理 webpack 内部实例的特定数据。
  • 功能完成后调用 webpack 提供的回调。

3. compiler 和 Compilation

4.plugin 实现和原理

4.1 DonePlugin 监听 webpack 编译完毕的插件 [done 都干完了]
// 监听编译完毕事件
class DonePlugin {
  constructor(options) {
    this.options = options;
  }
  apply(compiler) {
    // 每当编译[done写完了]完成后,都会call done这个事件
    compiler.hooks.done.tap('DonePlugin', () => {
      console.log(this.options.message || arguments);
    });
  }
}

module.exports = DonePlugin;
4.2 OptimizePlugin 监听 webpack 优化的时候的事件的插件
// 优化时监听插件,想知道如何或者compilation对象
class OptimizePlugin {
  constructor(options) {
    this.options = options;
  }
  apply(compiler) {
    // 每当compiler对象创建出来一个compilation编译对象,就会触发回调,回调的参数就是compilation
    compiler.hooks.compilation.tap('OptimizePlugin', compilation => {
      // 监听compilation对象的钩子
      compilation.hooks.optimize.tap('OptimizePlugin', () => {
        console.log('webpack编译对象正在优化中...');
      });
    });
  }
}

module.exports = OptimizePlugin;
4.3 EmitPlugin 监听 webpack 执行异步钩子的插件
// 优化插件,想知道如何或者compilation对象
class EmitPlugin {
  constructor(options) {
    this.options = options;
  }
  apply(compiler) {
    // AsyncSeriesHook 异步的串行钩子
    compiler.hooks.emit.tapAsync('EmitPlugin', (compilation, callback) => {
      console.log('开始执行EmitPlugin');
      setTimeout(() => {
        callback(); // 等待三秒
        console.log('结束执行EmitPlugin');
      }, 3000);
    });
  }
}
// promise 版本
class EmitPromisePlugin {
  constructor(options) {
    this.options = options;
  }
  apply(compiler) {
    // AsyncSeriesHook 异步的串行钩子
    compiler.hooks.emit.tapPromise('EmitPromisePlugin', compilation => {
      return new Promise((resolve, reject) => {
        console.log('开始执行EmitPlugin promise version');
        setTimeout(() => {
          resolve(); // 等待三秒
          console.log('结束执行EmitPlugin promise version');
        }, 3000);
      });
    });
  }
}
module.exports = { EmitPlugin, EmitPromisePlugin };
4.4 ZipPlugin 打包后的文件压缩成压缩包的插件
// 压缩文件的包
const JSZip = require('jszip');
// 引入webpack源码包
const { RawSource } = require('webpack-sources');
/**
 * 写插件的一般思路:[压缩文件成为压缩包的插件]
 * 1.找到写插件的代码,要执行的钩子
 * 2.要知道钩子函数的参数和数据结构,进行加工
 * 
 * compilation.assets : {
  'main.js': CachedSource {
    _source: ConcatSource { children: [Array] },
    _cachedSource: undefined,
    _cachedSize: 3887,
    _cachedMaps: {},
    node: [Function],
    listMap: [Function]
  },
  'index.html': { source: [Function: source], size: [Function: size] }
}
 */
class ZipPlugin {
  constructor(options) {
    this.options = options;
  }
  apply(compiler) {
    // 当资源准备就绪,准备向输出的目录里写入的时候会触发这个钩子
    compiler.hooks.emit.tapAsync('ZipPlugin', (compilation, callback) => {
      const zip = new JSZip();
      // 遍历文件名,拿到对象上的所有属性
      for (const filename in compilation.assets) {
        const content = compilation.assets[filename];
        // content.source()拿到资源
        zip.file(filename, content.source());
      }
      // 把所有的文件压成压缩包,得到压缩包的二进制内容 content
      zip.generateAsync({ type: 'nodebuffer' }).then(content => {
        // 往compilation.assets上挂新的属性和值,里面一定要由source,size两个对象
        // compilation.assets[this.options.name] = {
        //   source: () => content,
        //   size: () => content.length
        // };
        // 直接用RawSource类直接new
        compilation.assets[this.options.name] = new RawSource(content);
        // 然后调用执行下去,把compilation继续往下传递
        callback(null, compilation);
      });
    });
  }
}

module.exports = ZipPlugin;
4.5 preFetch-plugin 预获取
// /* webpackPrefetch: true */ 预加载
import(
  /* webpackPrefetch: true */ /* webpackChunkName: 'name' */ './hello'
).then(result => {
  console.log(result.default);
});
// plugins/PrefetchPlugin.js
class PrefetchPlugin {
  constructor(options) {
    this.options = options;
  }
  apply(compiler) {
    compiler.hooks.compilation.tap('PrefetchPlugin', compilation => {
      // 监听html-webpack-plugin插件产生的钩子,并直接修改html标签
      compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync(
        'PrefetchPlugin',
        (htmlData, callback) => {
          // head: [], // 往head里面插入两个对象,就是两个link标签
          // body: [ { tagName: 'script', closeTag: true, attributes: [Object] } ],
          // 找到懒加载,预获取的
          // 1.过滤哪些代码块是prefetch的
          const chunks = compilation.chunks; // 所有的代码块都放compilation的chunks属性
          const chunkObj = {}; // 把chunks数组变成对象
          chunks.forEach(chunk => {
            chunkObj[chunk.id] = chunk;
          });
          // 2.循环这个数组,拿到prefetch的代码块
          chunks.forEach(chunk => {
            const prefetchChunkIds = chunk.getChildIdsByOrders().prefetch;
            // 3.存在的要预获取
            if (prefetchChunkIds) {
              // 4.进行预处理
              prefetchChunkIds.forEach(prefetchChunkId => {
                // '--------------------------------------------',
                // todo 5.拿到上面的文件
                const files = chunkObj[prefetchChunkId].files;
                // 6.每个文件对应一个link标签
                files.forEach(file => {
                  // 7.然后放在html数据中的head里面
                  htmlData.head.push({
                    tagName: 'link',
                    closeTag: true,
                    attributes: {
                      as: 'script',
                      rel: 'prefetch',
                      href: file // 'hello.js'
                    }
                  });
                });
              });
            }
          });
          callback(null, htmlData);
        }
      );
    });
  }
}

module.exports = PrefetchPlugin;
/**
 * 1.获取compilation [手工插入link标签] 写html-webpack-plugin插件的插件
 * 2.监听钩子 compilation.hooks.htmlWebpackPluginAlterAssetTags = new AsyncSeriesWaterfallHook(['plugin'])
 */
 类似资料: