G2 项目线上和线下不一致:Tree Shaking 和 sideEffects 到底该怎么用?

G2 是蚂蚁集团 AntV 团队开源的企业级可视化框架,这周的某一天突然有三个业务方同时找到我,说项目中 G2 的图表线上和线下不一致:比如线上的一个饼图在改变图表大小的时候,会出现如下图"飞出去"的情况:

排查问题

这种线上和线下不一致的问题不太容易排查:用户没有办法给到一个最小的图表复现 Demo,不能排除很多非 G2 代码本身的问题,比如浏览器版本,使用的框架等。针对这种问题,比较好的办法就是让业务方给你一个预览链接,然后你自己去调试。

定位问题

否定了如下一些错误的猜想之后:

  • 浏览器版本太低
  • 本地和线上构建工具不一致
  • ...

突然发现线上打包后的代码少了一行很重要的代码:

js 复制代码
runtime.enableCSSParsing = false;

也就是 G2 代码中 src/index.ts 中的这一行代码,在打包后就消失了!

这里简单解释一下这行代码的作用:关闭底层渲染引擎 @antv/g 的解析 css 属性的能力。这个能力对 G2 的性能有较大的影响,所以需要关闭。如果不关闭,目前 G2 代码中的一些属性解析就会有问题,从而导致线上代码和线下的运行不一致。

那么问题就是:为啥这行打包之后就没了?

我突然意识到,这可能和这个 PR 新增的按需打包的能力有关系,package.json 中的 sideEffects 应该写错了!

json 复制代码
{
  "sideEffects": ["src/index.ts"]
}

什么是 Tree Shaking

在介绍 sideEffects 之前,得先了解一个概念:Tree Shaking。

Tree Shaking 这个概念最开始是打包工具 Rollup 提出来的,后来 Webpack 等打包工具也支持这个能力。简单来讲,就是一种在打包过程中去掉没有使用的代码(dead-code),从而减少代码体积的手段。需要注意的是,这里的源代码需要使用 ESM 模块系统。

比如使用了一个名叫 math.js 的第三方库,这个库导出了两个方法:

javascript 复制代码
// index.js
export function add(a, b) {
  return a + b;
}

export function sub(a, b) {
  return a - b;
}

在 vector.js 项目中只使用了 add 方法:

js 复制代码
// vectorAdd.js
import { add } from 'math.js'

export function vectorAdd([x, y], [x1, y1]) {
  return [add(x, x1), add(y, y1)]
}

打包工具默认会把 math.js 里面的 add 和 sub 都打包进来了,那如何不打包 sub 函数呢?这就需要指定 math.js 项目中 package.json 的 sideEffects 为 false:

json 复制代码
{
  "sideEffects": false
}

这样 sub 函数就不会出现在最后的构建产物里面了,这就是所谓的 Tree Shaking。

什么是 sideEffects

可是发现,为了使用 Tree Shaking 的能力,我们需要指定 package.json 的 sideEffects 字段,这字段是什么?

这个字段出现是因为:打包工具不能完全确定文件中哪些是无用代码,需要开发者提供更多信息。上面的 sub 函数可以很容易确定是无用代码,因为 vectorAdd.js 这个文件中没有任何函数依赖它。但是文件中没有任何函数依赖就是无用代码了吗?

答案是不是。

G2 中 src/index.ts 中的 runtime.enableCSSParsing = false; 就是很好的一个例子。src/index.ts 没有任何函数依赖它,但是它却不是无用代码:因为我们知道,@antv/g 的某些文件依赖了这个变量。换句话说,这行代码就是有副作用(SideEffect) 的,不能被 Tree Shaking 掉。

为了解决这个问题,sideEffects 这个字段就诞生了:指定项目有副作用的文件。

为了安全,默认项目中所有的文件都有副作用。如果项目中所有的文件都没有副作用,比如 math.js,就可以设置 sideEffects 为 false。如果项目中某些文件有副作用,那么可以通过一个数组指定有副作用的文件,而其余的文件都没有副作用,比如 G2 只用 src/index.ts 有副作用,就可以如下声明 sideEffects:

json 复制代码
{
  "sideEffects": ["src/index.ts"]
}

然后就出问题了!

解决办法

上面的写法乍一看完全没有问题,G2 代码确实就这个文件有副作用,但是别人项目使用的是你 src 下面的代码吗?其实不是,通过 G2 的 packge.json 可以发现,依赖项目中使用的应该是构建后 esm/index.js 的代码。

json 复制代码
{
  "module": "esm/index.js",
  "exports": {
    ".": {
      "import": "./esm/index.js",
    }
  },
}

这使得让打包工具误认为除了 src/index.ts 以外所有的文件都是没有副作用的,包括 esm/index.js 这个文件,所以那一行代码就被去掉了。

最后正确的写法应该是:

json 复制代码
{
  "sideEffects": ["./esm/index.js"]
}

收获

通过这次事情,可以得到一个教训:sideEffects 指定的路径一定是依赖项目使用的文件路径,或者是 package.json module 字段指定的文件路径。

对于 TypeScript 的项目,应该是你构建的产物的路径,因为 TypeScript 项目是一定要构建的。对于 JavaScript 项目,则也可能直接是 src 路径。

相关推荐
西***63474 天前
怕故障?怕扩展难?分布式可视化控制:给足场景安全感
分布式·数据可视化
Aloudata技术团队4 天前
以 NoETL 指标语义层为核心:打造可信、智能的 Data Agent 产品实践
数据挖掘·数据分析·数据可视化
爱思德学术4 天前
中国计算机学会(CCF)推荐学术会议-B(数据库/数据挖掘/内容检索):PODS 2026
数据库·数据分析·数据可视化·数据库系统
Serendipity_Carl7 天前
爬虫数据清洗可视化案例之全球灾害数据
爬虫·python·pycharm·数据可视化·数据清洗
青云交7 天前
Java 大视界 -- Java 大数据在智能建筑能耗监测与节能策略制定中的应用
数据分析·数据存储·数据可视化·1024程序员节·能耗监测·java 大数据·智能建筑
M0066888 天前
从拖拽到架构:低代码如何兼顾速度、灵活性与可控边界
低代码·数据可视化
麦麦大数据8 天前
F032 材料科学文献知识图谱可视化分析系统(四种知识图谱可视化布局) | vue + flask + echarts + d3.js 实现
vue.js·flask·知识图谱·数据可视化·论文文献·1024程序员节·科研图谱
JEECG低代码平台10 天前
JimuReport 积木报表 v2.1.5 版本发布,免费的可视化报表和大屏
报表·数据可视化·打印报表
爱思德学术10 天前
EI会议:第三届大数据、计算智能与应用国际会议(BDCIA 2025)
大数据·机器学习·数据可视化·计算智能
图扑可视化10 天前
WebGL/Canvas 内存泄露分析
数字孪生·数据可视化·技术·内存泄露