从 Rollup 到 Tsdown:Cesium 二次开发项目的打包优化实践(1 分钟→5 秒)

从 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-aliasrollup-plugin-copy等插件,直接通过aliascopy配置实现;
  • 环境变量替换、模块解析等功能内置,减少依赖体积;
  • 保留了 Rollup 的插件兼容能力(如unplugin-auto-importrollup-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.0rollup@4.6.1typescript@5.8.3,版本差异可能导致配置不兼容。

五、总结与建议

核心优势

  1. 速度极快:打包时间从 1 分钟缩短至 5 秒,大幅提升开发效率;
  2. 配置简洁:内置常用功能,减少插件依赖,降低维护成本;
  3. 兼容友好:支持 Rollup 插件,迁移成本低。

适用场景

  • 优先推荐用于开发环境,提升调试体验;
  • 生产环境需谨慎(Tsdown 尚未发布正式版,可能存在不稳定因素)。

迁移注意事项

  1. 检查代码中 “类属性 + 原型链” 的使用场景,避免实例属性覆盖问题;
  2. 部分 Rollup 插件可能存在兼容问题,建议逐步迁移并测试;
  3. 生成类型声明需依赖 TypeScript,确保已安装并配置tsconfig.json

通过本次切换,Tsdown 在项目中展现了显著的效率优势。期待正式版发布后,能进一步完善稳定性,成为生产环境的可靠选择。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值