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

相关推荐
datagear20 小时前
如何在DataGear 5.4.1 中快速制作HTTP数据源服务端分页的数据表格看板
javascript·数据可视化
wenzhangli71 天前
OneCode 图表组件核心优势解析
数据可视化
镜舟科技2 天前
StarRocks × Tableau 连接器完整使用指南 | 高效数据分析从连接开始
starrocks·数据分析·数据可视化·tableau·连接器·交互式分析·mpp 数据库
永洪科技2 天前
永洪科技荣获商业智能品牌影响力奖,全力打造”AI+决策”引擎
大数据·人工智能·科技·数据分析·数据可视化·bi
Codebee3 天前
OneCode图表配置速查手册
大数据·前端·数据可视化
DataGear3 天前
如何在DataGear 5.4.1 中快速制作SQL服务端分页的数据表格看板
javascript·数据库·sql·信息可视化·数据分析·echarts·数据可视化
程序员阿超的博客4 天前
Python 数据分析与机器学习入门 (五):Matplotlib 数据可视化基础
python·信息可视化·数据分析·matplotlib·数据可视化·python教程·pyplot
大数据CLUB4 天前
基于spark的航班价格分析预测及可视化
大数据·hadoop·分布式·数据分析·spark·数据可视化
镜舟科技4 天前
数据可视化:5 分钟读懂其核心价值与技术实践
starrocks·数据可视化·用户行为分析·商业智能·自然语言交互·商业价值·跨部门协作
云天徽上13 天前
【目标检测】图像处理基础:像素、分辨率与图像格式解析
图像处理·人工智能·目标检测·计算机视觉·数据可视化