•
阅读时长 2 分钟

写了 export,为什么打包后有的代码还在

目录

只 import 了一个函数,另一个函数为什么还出现在打包文件里?排查 Tree Shaking 时,需要看构建输入和最终产物,而不是看到 export 就默认其他代码都能被删掉。

ES Module 提供静态的导入导出关系,构建工具可以分析哪些导出被使用。分析出没用的代码,以及最后删掉这些代码,也可能发生在不同阶段。

用一个标记检查生产产物

在空目录运行 npm init -y 和 npm install —save-dev —save-exact webpack@5.99.9。下面用 Node API 构建,不依赖全局 webpack 命令。

创建 math.js:

export const used = (a, b) => a + b;
export function unused() {
  return "UNUSED_MARKER_FOR_EXPERIMENT";
}

创建 entry.js,只调用 used:

import { used } from "./math.js";
globalThis.result = used(2, 3);

创建 build.cjs:

const webpack = require("webpack");
const path = require("path");
webpack(
  {
    mode: "production",
    entry: path.resolve("entry.js"),
    output: {
      path: path.resolve("dist"),
      filename: "main.js",
      chunkFilename: "[id].js",
    },
    devtool: false,
    optimization: { usedExports: true },
  },
  (error, stats) => {
    if (error || stats.hasErrors()) {
      console.error(error || stats.toString("errors-only"));
      process.exitCode = 1;
      return;
    }
    console.log(
      JSON.stringify(
        stats.toJson({
          all: false,
          assets: true,
          chunks: true,
          modules: true,
          usedExports: true,
        }),
        null,
        2,
      ),
    );
  },
);

运行 node build.cjs。再创建 check.cjs,检查真正输出的 JS:

const fs = require("fs");
const assert = require("assert").strict;
const vm = require("vm");
const code = fs.readFileSync("dist/main.js", "utf8");
assert.ok(!code.includes("UNUSED_MARKER_FOR_EXPERIMENT"));
const context = {};
vm.runInNewContext(code, context);
assert.equal(context.result, 5);
console.log("未使用函数已移除,使用的函数仍然正常");

这个最小例子里,生产产物没有 marker,执行结果仍然是 5。VM 只用来执行自己写的固定产物,不作为运行未知代码的安全沙箱。

顶层代码不能只看有没有导出

模块里如果有 console.log、全局注册或者样式导入,即使导出的变量没人读,加载模块仍可能有作用。不能把“导出没用”直接当成“整个模块能删”。

sideEffects: false 是对文件副作用的声明,不是可以给所有包开启的万能优化。带 CSS 或初始化代码的文件被错误标记时,生产构建可能丢掉需要的行为。

把这些配置加上以前,需要确认输入还保留 ESM。Babel 提前转换成 CommonJS、动态访问导出,或者库本身提供的入口不同,都可能影响分析。

看到代码还在,检查的对象要对

开发构建为了调试,可能保留未压缩的代码;source map 也可能包含原始源码。map 的 sourcesContent 出现函数,不等于部署的 JS 还在执行它。

可以从生产 JS、usedExports 和模块副作用一步步查。这个例子没有比较体积百分比,只检查不需要的函数消失、需要的行为保留。配置说明参考 Webpack Tree Shaking 文档。