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

相关推荐
牛猫Data44 分钟前
Power BI如何连接Azure Databricks数据源?
microsoft·数据分析·azure·数据可视化·powerbi
B站计算机毕业设计超人2 小时前
计算机毕业设计Python+Vue.js游戏推荐系统 Steam游戏推荐系统 Django Flask 游 戏可视化 游戏数据分析 游戏大数据 爬虫
大数据·hadoop·算法·机器学习·spark·网络爬虫·数据可视化
几米哥2 天前
Python数据可视化进阶:打造高质量图表的完整指南
数据可视化
B站计算机毕业设计超人3 天前
计算机毕业设计Hadoop+Spark美团美食推荐系统 美团餐厅推荐系统 美团推荐系统 美食价格预测 美团爬虫 美食数据分析 美食可视化大屏
大数据·hadoop·python·机器学习·课程设计·数据可视化·推荐算法
HsuHeinrich3 天前
流程图(三)利用python绘制桑基图
python·数据可视化
qingyunliushuiyu4 天前
数据可视化搭配数据分析,解锁数据潜能的密码
数据挖掘·数据分析·数据可视化·数据可视化系统·数据分析系统