开源贡献: 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
相关推荐
爱吃青椒不爱吃西红柿‍️5 分钟前
华为ASP与CSP是什么?
服务器·前端·数据库
一棵开花的树,枝芽无限靠近你9 分钟前
【PPTist】添加PPT模版
前端·学习·编辑器·html
陈王卜11 分钟前
django+boostrap实现发布博客权限控制
java·前端·django
景天科技苑19 分钟前
【vue3+vite】新一代vue脚手架工具vite,助力前端开发更快捷更高效
前端·javascript·vue.js·vite·vue项目·脚手架工具
SameX21 分钟前
HarmonyOS Next 安全生态构建与展望
前端·harmonyos
小行星12530 分钟前
前端预览pdf文件流
前端·javascript·vue.js
小行星12537 分钟前
前端把dom页面转为pdf文件下载和弹窗预览
前端·javascript·vue.js·pdf
疯狂的沙粒40 分钟前
如何在 React 项目中应用 TypeScript?应该注意那些点?结合实际项目示例及代码进行讲解!
react.js·typescript
Lysun0011 小时前
[less] Operation on an invalid type
前端·vue·less·sass·scss
J总裁的小芒果1 小时前
Vue3 el-table 默认选中 传入的数组
前端·javascript·elementui·typescript