从 Rollup 到 Tsdown:Cesium 二次开发项目的打包优化实践(1 分钟→5 秒)
引言
在公司的数字孪生项目中,我们基于 Cesium 进行二次开发,需要通过打包工具生成多版本的可复用包。由于项目采用 TypeScript 开发,最初选择了 Rollup 4 作为打包工具 —— 它能满足 TS 编译和多格式输出的需求,但随着代码量增长,打包速度逐渐变慢至 1 分钟以上,严重影响开发效率(调试时大半时间浪费在等待打包上)。
偶然发现尤雨溪团队开发的新工具 Tsdown(基于 Rolldown,兼容 Rollup 插件),尝试切换后打包速度显著提升:从 1 分钟缩短至 5 秒左右(初次打包稍慢,后续增量打包稳定在 5 秒内)。本文记录这次切换的过程、配置对比、遇到的问题及解决方案,供同类项目参考。
⚠️ 提示:Tsdown 目前仍为测试版,功能未完全稳定,建议谨慎用于生产环境。
一、Tsdown 简介
Tsdown 是基于 Rolldown 的前端打包工具,由 Vue 团队核心成员开发,主打极速打包和Rollup 兼容。它内置了常用功能(如模块解析、代码压缩、文件复制等),无需额外安装大量插件,配置更简洁;同时支持大多数 Rollup 插件,可实现无缝迁移。
二、从 Rollup 到 Tsdown 的配置对比
1. Rollup 配置(rollup.config.ts)
import json from '@rollup/plugin-json';
import commonjs from '@rollup/plugin-commonjs';
import typescript from '@rollup/plugin-typescript';
import resolve from '@rollup/plugin-node-resolve';
import alias from '@rollup/plugin-alias';
import terser from '@rollup/plugin-terser';
import strip from '@rollup/plugin-strip';
import copy from 'rollup-plugin-copy';
import clear from 'rollup-plugin-clear';
import postcss from 'rollup-plugin-postcss';
import autoprefixer from 'autoprefixer';
import cssnano from 'cssnano';
import { RollupOptions } from 'rollup';
import { URL, fileURLToPath } from 'node:url';
import AutoImport from 'unplugin-auto-import/rollup'; //自动导入api
import { atuoImport } from './rollup.common';
import { name } from './config';
import replace from '@rollup/plugin-replace';
const libName = name;
const distpath = `${process.env.DistPath}${libName}`;
export default <RollupOptions>{
input: './src/index.ts',
external: ['cesium', 'axios'], // 排除外部依赖模块
output: [
{
file: `${distpath}/${libName}.es.js`,
format: 'es',
globals: {
cesium: 'Cesium',
axios: 'axios'
}
},
{
file: `${distpath}/${libName}.cjs`,
format: 'cjs',
name: libName,
globals: {
cesium: 'Cesium',
axios: 'axios'
}
}
],
plugins: [
alias({
entries: [
{
find: '@',
replacement: fileURLToPath(new URL('./src', import.meta.url))
}
]
}),
AutoImport(atuoImport),
clear({
targets: ['dist']
}),
resolve(),
json(),
commonjs(),
replace({ preventAssignment: true, values: { 'process.env.PermissionCode': JSON.stringify(process.env.PermissionCode) } }),
typescript({
sourceMap: false,
noForceEmit: false,
tsconfig: './tsconfig.json' // 指定 TypeScript 配置文件路径
}),
postcss({
minimize: true,
plugins: [autoprefixer({ cascade: false }), cssnano()],
sourceMap: false,
// 通过此回调函数设置输出路径和文件名
extract: 'assets/style/index.css',
use: {
sass: {
silenceDeprecations: ['legacy-js-api']
}
} as any
}),
strip({
include: ['**/*.js', '*.js', '**/*.ts', '*.ts'],
labels: ['unittest'],
exclude: ['./src/Core/utils/copyright/index.ts'],
functions: [
'console.log',
'console.time',
'console.timeEnd',
'console.dir',
'console.trace',
'console.assert',
'console.info',
'console.table',
'console.debug',
'console.count',
'ceshi'
]
}),
terser({
ecma: 2019,
format: {
comments: false
},
maxWorkers: 4
}),
copy({
targets: [
{
src: 'public/*',
dest: `${distpath}/`
},
{
src: 'node_modules/cesium/Build/Cesium/Workers',
dest: `${distpath}/assets/Cesium`
},
{
src: 'node_modules/cesium/Build/Cesium/ThirdParty',
dest: `${distpath}/assets/Cesium`
},
{
src: 'node_modules/cesium/Build/Cesium/Assets',
dest: `${distpath}/assets/Cesium`
},
{
src: 'node_modules/cesium/Build/Cesium/Cesium.js',
dest: `${distpath}/assets/Cesium`,
rename: (_, extension) => `index.${extension}`
},
{
src: 'node_modules/axios/dist/axios.min.js',
dest: `${distpath}/assets/DependencyPackages/axios`,
rename: (_, extension) => `index.${extension}`
}
]
})
],
onwarn: (warning, warn) => {
if (warning.code === 'EVAL') return; // 忽略 EVAL 类型的警告
warn(warning);
}
};
2. Tsdown 配置(tsdown.config.ts)
import { defineConfig } from 'tsdown';
import { config } from 'dotenv';
import { name, atuoImport } from './config';
import { URL, fileURLToPath } from 'node:url';
import AutoImport from 'unplugin-auto-import/rollup'; //自动导入api
import postcss from 'rollup-plugin-postcss';
import autoprefixer from 'autoprefixer';
if (process.env.NODE_ENV === 'development') {
config({ path: '.env.development' });
} else {
config({ path: '.env.production' });
}
const libName = name;
const distpath = `${process.env.DistPath}${libName}`;
export default defineConfig({
entry: ['./src/index.ts'],
outDir: distpath,
external: ['cesium', 'axios'], // 排除外部依赖模块
noExternal: ['proj4', 'kdbush'], //不排除的外部依赖
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
env: { PermissionCode: process.env.PermissionCode },
globalName: '_3umap',
outputOptions: { globals: { cesium: 'Cesium', axios: 'axios' } },
platform: 'browser', //运行平台
plugins: [
AutoImport(atuoImport),
postcss({
minimize: true,
plugins: [autoprefixer({ cascade: false })],
sourceMap: false,
// 通过此回调函数设置输出路径和文件名
extract: 'index.css',
use: {
sass: {
silenceDeprecations: ['legacy-js-api']
}
} as any
})
],
copy: [
{
from: 'public',
to: `${distpath}`
},
{
from: 'node_modules/cesium/Build/Cesium/Workers',
to: `${distpath}/assets/Cesium/Workers`
},
{
from: 'node_modules/cesium/Build/Cesium/ThirdParty',
to: `${distpath}/assets/Cesium/ThirdParty`
},
{
from: 'node_modules/cesium/Build/Cesium/Assets',
to: `${distpath}/assets/Cesium/Assets`
},
{
from: 'node_modules/cesium/Build/Cesium/Cesium.js',
to: `${distpath}/assets/Cesium/index.js`
},
{
from: 'node_modules/axios/dist/axios.min.js',
to: `${distpath}/assets/DependencyPackages/axios.js`
}
]
});
配置差异说明
Tsdown 的配置更简洁,核心原因是内置了 Rollup 需要插件实现的功能:
- 无需安装
@rollup/plugin-alias、rollup-plugin-copy等插件,直接通过alias、copy配置实现; - 环境变量替换、模块解析等功能内置,减少依赖体积;
- 保留了 Rollup 的插件兼容能力(如
unplugin-auto-import、rollup-plugin-postcss可直接复用)。
三、遇到的问题及解决方案
1. 问题现象
切换 Tsdown 后,代码运行时出现死循环,浏览器标签页无响应。经调试发现,问题源于类(Class)属性的转换差异。
2. 根源分析
TypeScript 与 Tsdown 对未初始化的类属性处理不同:
-
TypeScript 转换:会自动抹除未初始化的属性(不生成实例属性)。
class Test { public name!:string constructor(){} } /** 转换结果 */ "use strict"; class Test { // 转换后(无name实例属性,可通过原型链访问) constructor() { } } -
Tsdown 转换:会保留未初始化的属性(生成实例属性,值为
undefined)。class Test { public name!:string constructor(){} } // 转换后(存在name实例属性,覆盖原型属性) "use strict"; class Test { name; // 实例属性,值为undefined constructor() {} }
在我们的项目中,代码通过原型链扩展类属性(如Test.prototype.name = 'default'),而 Tsdown 生成的实例属性name会覆盖原型属性,导致依赖原型链的逻辑异常,最终引发死循环。
3. 解决方案
不再向prototype上拓展属性,直接初始化到类上(原本拓展的目的是不重复创建属性,并且还能获得类型提示)
我向 tsdown 提交了 bug 目前还没回应,有回应会更新的

四、打包命令与依赖说明
1. 脚本命令(package.json)
"scripts": {
"build": "cross-env NODE_ENV=production tsdown --format esm --minify --dts && pnpm worker",
// 生产环境打包:生成ES模块、压缩代码、生成类型声明,再打包worker
"iife": "cross-env NODE_ENV=production tsdown --format iife --minify --dts && pnpm worker",
// 生成立即执行函数(IIFE)格式
"cjs": "cross-env NODE_ENV=production tsdown --format cjs --minify --dts && pnpm worker",
// 生成CommonJS格式
"dev": "cross-env NODE_ENV=development tsdown --format iife --sourcemap && pnpm workerDev",
// 开发环境:生成sourcemap方便调试
"worker": "cross-env NODE_ENV=production tsdown --config ./tsdown.config.worker.ts --format esm",
// 打包worker脚本(ES模块)
"workerDev": "cross-env NODE_ENV=development tsdown --config ./tsdown.config.worker.ts --format esm --sourcemap"
// 开发环境打包worker
}
2. 依赖安装
# 安装Tsdown(开发依赖)
pnpm add tsdown -D
# 安装TypeScript(用于生成dts类型声明)
pnpm add typescript -D
# 安装环境变量管理工具
pnpm add cross-env -D
注意:本文使用版本为
tsdown@0.13.0、rollup@4.6.1、typescript@5.8.3,版本差异可能导致配置不兼容。
五、总结与建议
核心优势
- 速度极快:打包时间从 1 分钟缩短至 5 秒,大幅提升开发效率;
- 配置简洁:内置常用功能,减少插件依赖,降低维护成本;
- 兼容友好:支持 Rollup 插件,迁移成本低。
适用场景
- 优先推荐用于开发环境,提升调试体验;
- 生产环境需谨慎(Tsdown 尚未发布正式版,可能存在不稳定因素)。
迁移注意事项
- 检查代码中 “类属性 + 原型链” 的使用场景,避免实例属性覆盖问题;
- 部分 Rollup 插件可能存在兼容问题,建议逐步迁移并测试;
- 生成类型声明需依赖 TypeScript,确保已安装并配置
tsconfig.json。
通过本次切换,Tsdown 在项目中展现了显著的效率优势。期待正式版发布后,能进一步完善稳定性,成为生产环境的可靠选择。
1199

被折叠的 条评论
为什么被折叠?



