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 路径。

相关推荐
安冬的码畜日常2 小时前
【D3.js in Action 3 精译_029】3.5 给 D3 条形图加注图表标签(上)
开发语言·前端·javascript·信息可视化·数据可视化·d3.js
安冬的码畜日常6 小时前
【D3.js in Action 3 精译_027】3.4 让 D3 数据适应屏幕(下)—— D3 分段比例尺的用法
前端·javascript·信息可视化·数据可视化·d3.js·d3比例尺·分段比例尺
搞大屏的小北 BI8 小时前
国内旅游:现状与未来趋势分析
信息可视化·数据分析·旅游·数据可视化·bi 工具
bin915318 小时前
【EXCEL数据处理】000010 案列 EXCEL文本型和常规型转换。使用的软件是微软的Excel操作的。处理数据的目的是让数据更直观的显示出来,方便查看。
大数据·数据库·信息可视化·数据挖掘·数据分析·excel·数据可视化
Death20020 小时前
Qt 6 相比 Qt 5 的主要提升与更新
开发语言·c++·qt·交互·数据可视化
bin91531 天前
【EXCEL数据处理】000009 案列 EXCEL单元格数字格式。文本型数字格式和常规型数字格式的区别
大数据·前端·数据库·信息可视化·数据分析·excel·数据可视化
安冬的码畜日常2 天前
【D3.js in Action 3 精译_028】3.4 小节 DIY 实战:使用 Observable 在线绘制 D3 条形图
前端·javascript·信息可视化·数据可视化·d3.js·observable
FUXI_Willard2 天前
MATLAB绘图基础9:多变量图形绘制
开发语言·matlab·信息可视化·数据可视化·matlab绘图
bin91532 天前
【EXCEL数据处理】000014 案例 EXCEL分类汇总、定位和创建组。附多个操作案例。
信息可视化·数据挖掘·数据分析·excel·数据可视化·数据图表·excel 数据分析
bin91532 天前
【EXCEL数据处理】000011 案列 EXCEL带有三角形图标的单元格转换,和文本日期格式转换。
大数据·数据库·信息可视化·数据挖掘·数据分析·excel·数据可视化