webpack-bundle-analyzer 核心实现解析

2022-06-15 08:50:18 浏览数 (1)

webpack-bundle-analyzer

webpack-bundle-analyzer 是一个插件:通过分析构建产物,最终生成 矩形树图 方便开发者根据项目构建后的依赖关系以及实际的文件尺寸,来进行相应的性能优化。

为什么要研究这个插件?因为纵观当前的几类依赖分析的插件,包括 webpack 自身提供的一个工具 http://webpack.github.io/analyse/ 从可视化的角度来说,都没有 webpack-bundle-analyzer 提供的矩形树图来的直观:

  1. webpack visualizer
  2. webpack chart

以上几个工具都是只分析 stats.json 暴露的内容来生成图表。

而 webpack-bundle-analyzer 和他们之间的区别在于借助 acorn ,通过分析构建产物来得出模块依赖关系,核心实现上其实是脱离了 webpack 的能力,但由于是分析 webpack 的构建产物,因而要对打包出来的 js 内容的组装结构需要了解,随着 webpack 的不断升级,产物结构也会随之发生改变,因而需要不断的兼容,通过阅读源码以及作者的注释可以看到,webpack v5 的产物已经比较难以分析了

在看之前,需要先了解以下几个知识点:

  • Webpack 提供的 stats 属性中 modulechunkassets的含义。
  • acorn:一个完全使用 Javascript 实现的,小型且快速的 Javascript 解析器 ,AST 抽象语法树相关知识。

核心的流程如下:

插件入口

首先是遵循 Webpack 插件的写法,在 done 函数里获取到 stats

代码语言:javascript复制
class BundleAnalyzerPlugin {
    apply(compiler) {
        // 核心实现入口
        const done = (stats, callback) => {/* ... */ }
        // 兼容 webpack 新老版本的写法
        if (compiler.hooks) {
            compiler.hooks.done.tapAsync('webpack-bundle-analyzer', done);
        } else {
            compiler.plugin('done', done);
        }
    }
}

webpack-bundle-analyzer 插件的数据源取自 stats.toJson() 这个方法,而生成图表数据的函数则是 getViewerData(),下面拆分了解这个函数的具体实现。

1. 确认资源的结构

代码语言:javascript复制
const _ = require('lodash');
const FILENAME_QUERY_REGEXP = /?.*$/u;
const FILENAME_EXTENSIONS = /.(js|mjs)$/iu;

函数入参为 bundleStats

// Sometimes all the information is located in `children` array (e.g. problem in #10)
// assets 为空 && children 存在,这种情况下资源信息都在 children 属性当中
if (_.isEmpty(bundleStats.assets) && !_.isEmpty(bundleStats.children)) {
  const { children } = bundleStats;
  bundleStats = bundleStats.children[0];
  // Sometimes if there are additional child chunks produced add them as child assets,
  // leave the 1st one as that is considered the 'root' asset.
  // 这种情况下 children 数组中的第一个元素当作根节点,children 数组中如果还有 assets,则 push 到 bundleStats.assets 中
  for (let i = 1; i < children.length; i  ) {
    children[i].assets.forEach((asset) => {
      asset.isChild = true;
      bundleStats.assets.push(asset);
    });
  }
} else if (!_.isEmpty(bundleStats.children)) {
  // Sometimes if there are additional child chunks produced add them as child assets
  bundleStats.children.forEach((child) => {
    child.assets.forEach((asset) => {
      asset.isChild = true;
      bundleStats.assets.push(asset);
    });
  });
}
// Picking only `*.js or *.mjs` assets from bundle that has non-empty `chunks` array
// 过滤出 *.js 和 *.mjs 并且 chunks 不为空的 assets
bundleStats.assets = bundleStats.assets.filter(asset => {
  // Filter out non 'asset' type asset if type is provided (Webpack 5 add a type to indicate asset types)
  if (asset.type && asset.type !== 'asset') {
    return false;
  }

  // Removing query part from filename (yes, somebody uses it for some reason and Webpack supports it)
  // See #22
  asset.name = asset.name.replace(FILENAME_QUERY_REGEXP, '');

  return FILENAME_EXTENSIONS.test(asset.name) && !_.isEmpty(asset.chunks);
});

拿一个项目来举例,图片里的内容是取自 stats.json() ,上面这段代码最后提取的产物是图中标注的三个对象:

2. 使用 Acorn AST 分析

接着开始遍历一个数组,数组中的内容是上面的三个对象。

首先判断 compiler.outputPath 是否存在?存在就用 acorn 库解析 JS 文件,调用 acorn-walkrecursive 方法递归处理解析后的 AST 树

0 人点赞