开源贡献: antv/dataset瘦身, 从1.78M到1.71KB

遇到的问题: 项目中使用到了DataSet上的一些transform 方法来处理数据, 但是transform是 Dataset.prototype上的,无法单独导出,但是包体积很大,足足有 660.83 kB, 因此需要给Dataset瘦身,以便支持treeshaking

先看存在的问题, 再讨论解决方法, 这是transform文件结构:

jsx 复制代码
// transform/waffle.js
function transform(dataView: View, options: Options):void
DataSet.registerTransform('map', transform);

首先想到的解决方法: 通过让用户去调用 DataSet.registerTransform来注册的方式减少体积,类似于 ag-grid module

js 复制代码
import { MasterDetailModule } from "@ag-grid-enterprise/master-detail";

ModuleRegistry.registerModules([
    ClientSideRowModelModule,
    CsvExportModule,
    ExcelExportModule,
    MasterDetailModule
]);

但是效果不好, 原因是transform仅占整个 Dataset.prototype 的很少一部分,优化效果有限

考虑第二个方法: 把transform单独打包出一份

import { map } from "@antv/data-set";

考虑到语义化和将来提取其他的prototype时的命名问题, 因此放到./transform 路径上更好:

import { map } from "@antv/data-set/transform";

需要处理的点:

  1. 参数依赖了 View, 需要解耦成纯函数,再使用适配器模式包裹纯函数返回新的函数,新函数入参为View,此外如果有用到View上的api,则重新实现
  2. transform是在每一个文件里注册的,这是副作用的操作, 会影响产物, 需要迁移到其他地方

处理 transform/map.js

diff 复制代码
- DataSet.registerTransform('map', (dataView: View, options: Options) => {
- dataView.rows = dataView.rows.map(options.callback || defaultCallback);
- });
+ const map = (items: any[], options: Options): any[] => {
+  const rows = [...(items || [])];
+  return (rows || []).map(options.callback || defaultCallback);
+ };

+ const mapTransform = (dataView: any, options: Options): void => {
+  dataView.rows = map(dataView.rows, options);
+ };
+ export { map, mapTransform };

处理 src/index.js

diff 复制代码
import { DataSet } from './data-set';
- import './transform/map';
+ import { mapTransform } from './transform/map';
+ DataSet.registerTransform('map', mapTransform);

export = DataSet;

接下来就是打包

diff 复制代码
# package.json
- "build": "rm -rf build && webpack",
+ "build": "rm -rf build  && tsup  && webpack",
+  "exports": {
+    ".": {
+      "import": "./build/data-set.js",
+      "require": "./build/data-set.js",
+      "types": "./lib/src/index.d.ts"
+    },
+    "./transform": {
+      "import": "./build/transform/index.mjs",
+      "require": "./build/transform/index.js",
+      "types": "./lib/src/transform/index.d.ts"
+    },
+    "./*": {
+      "import": "./build/*.mjs",
+      "require": "./build/*.js",
+      "types": "./build/*.d.ts"
+    }
+  }

新建 tsup.config.ts, 做多入口打包

jsx 复制代码
import { defineConfig } from 'tsup';
import { globSync } from 'glob';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

export default defineConfig({
  entry: Object.fromEntries(
    globSync('src/transform/**/*.ts').map((file) => {
      return [
        path.relative('src', file.slice(0, file.length - path.extname(file).length)),
        fileURLToPath(new URL(file, import.meta.url)),
      ];
    })
  ),
  format: ['esm', 'cjs'],
  outDir: 'build',
  clean: true,
  dts: true,
});

到这里基本就结束了, 然后 npm link, 找个项目测试一下

import { map } from "@antv/data-set/transform";

当打包项目时, 问题出现了, treeshaking 没生效,只引用了map但是另一个transform dagre的依赖dagre却进来了,而且没有使用到lodash, 却也被包含进来了

调试步骤:

注释 src/transform/index.ts 中的 export { dagre } from './diagram/dagre', 项目打包后就没有dagre 和lodash的依赖了, 原因就是出在dagre这个包,检查 node_modules/dagre/index 源码, 发现这是一个cjs的库

js 复制代码
// dagre/index.js
module.exports = {
  graphlib: require("./lib/graphlib"),

![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/adaed999f1d54b628b9809372fb917eb~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=2616&h=1434&s=324491&e=png&a=1&b=924447)
  layout: require("./lib/layout"),
  debug: require("./lib/debug"),
  util: {
    time: require("./lib/util").time,
    notime: require("./lib/util").notime
  },
  version: require("./lib/version")
};

原因也就知道了, 因为cjs不支持treeshaking, 又因为tsup默认会把第三方依赖都 external, 所以也不会转为esm, 一个解决办法就是打包时处理下这个模块,把 dagre 添加到tsup的 noExternal

diff 复制代码
# tsup.config.ts
+  noExternal: ['dagre'],

现在项目打包就正常了, 问题解决

之后, simple-statistics的依赖无法处理了, vite 好像也支持 import * as simpleStatistics from 'simple-statistics'的用法, 但是由于在运行时这要使用到的 simpleStatistics[method] 无法treeshaking

处理: d3-sankey的依赖,查看 transform/sankey.ts

js 复制代码
// transform/sankey.js
import { sankey } from 'd3-sankey';
// sankey/index.js
import {default as sankey} from './sankey'
// sankey/sankey.js
export default funtion sankey(){}

直接导出 不支持treeshaking

还有些transform, 对DataSet有依赖, 很难treeshaking

js 复制代码
DataSet.CONSTANTS.STATISTICS_METHODS.forEach((method) => {
  aggregates[method] = (data: any[], field: string) => {
    let values = data.map((row) => row[field]);
    if (isArray(values) && isArray(values[0])) {
      values = flattenDeep(values);
    }
    // @ts-ignore
    return simpleStatistics[method](values);
  };
});

总结:

改动之前 data-set包:1.78M

改动之后:

  • data-set: 1.08M,
  • import { map } from "@antv/data-set/transform"; 方式使用map: 368KB
  • import { map } from "@antv/data-set/transform/map";方式使用map: 1.71KB
相关推荐
EricWang135811 分钟前
[OS] 项目三-2-proc.c: exit(int status)
服务器·c语言·前端
September_ning11 分钟前
React.lazy() 懒加载
前端·react.js·前端框架
web行路人21 分钟前
React中类组件和函数组件的理解和区别
前端·javascript·react.js·前端框架
番茄小酱00122 分钟前
Expo|ReactNative 中实现扫描二维码功能
javascript·react native·react.js
超雄代码狂43 分钟前
ajax关于axios库的运用小案例
前端·javascript·ajax
长弓三石1 小时前
鸿蒙网络编程系列44-仓颉版HttpRequest上传文件示例
前端·网络·华为·harmonyos·鸿蒙
小马哥编程1 小时前
【前端基础】CSS基础
前端·css
嚣张农民1 小时前
推荐3个实用的760°全景框架
前端·vue.js·程序员
周亚鑫1 小时前
vue3 pdf base64转成文件流打开
前端·javascript·pdf
Justinc.2 小时前
CSS3新增边框属性(五)
前端·css·css3