遇到的问题: 项目中使用到了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";
需要处理的点:
- 参数依赖了 View, 需要解耦成纯函数,再使用适配器模式包裹纯函数返回新的函数,新函数入参为View,此外如果有用到View上的api,则重新实现
- 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: 368KBimport { map } from "@antv/data-set/transform/map";
方式使用map: 1.71KB