DTeam 技术日志

Doer、Delivery、Dream

近期 Typescript / Javascript 工具探索

冯宇 Posted at — Mar 28, 2024 阅读

随着时间的推进,是时候翻新我们的 js / ts 工具链的时候了,对此我们进行了一些探索。

Toolchain manager

Volta 是最近在 github 上发现的 node 版本管理工具,基于 rust 开发,速度非常快,整体功能和 nvm 非常接近。

基本上可看作是 nvm 的部分增强版。

Bundler

在 Node 项目开发中,我们需要对依赖进行 bundle 操作,最近我们将评估几种 bundle 工具。主要有 @vercel/ncc, swc, tsupbun

其中 @vercel/ncc 在我们的项目中已经使用了 2 年,只不过最近一年中 ncc 的开发近乎于停滞阶段,所以我们需要对比一下其他的工具。

总体概览

经过评估之后,我大体上总结了一下这几个工具的特点:

@vercel/ncc swc tsup bun
开发语言 JavaScript Rust TypeScript Zig, C++
底层 webpack esbuild / rollup
速度 极快 极快
bundle 功能 ☑️ (实验性)
bundle 范围 项目代码, 依赖, assets, napi 项目代码,依赖 项目代码, 依赖 项目代码,依赖
项目活跃度 ⭐️⭐️(目前停滞) ⭐️⭐️⭐️⭐️⭐️ (bundle 功能 ⭐️) ⭐️⭐️⭐️⭐️ ⭐️⭐️⭐️⭐️⭐️
github stars ~9k ~30k ~7.9k ~69k

总结:

依赖打包

目前这几个工具都能正确处理依赖打包问题,不过需要注意的是部分工具需要正确配置,例如 tsup 需要将要打包的依赖写入 devDependencies,对于 dependenciespeerDependencies 的依赖是不会 bundle 的。

Assets Relocation

这个功能不得不提,在 bundle 非常重要,需要在构建的时候将使用的资源文件一并打包,并且在产品环境中可以正确调用。

在 ncc 中这个功能是开箱即用的,具体参见 Asset Relocation 文档,了解它是如何工作的。

我们先来看一个范例,假设我们的项目中包含一个图片,我们的代码中需要使用这个图片,那么我们可以这样写:

const pngFile = path.join(__dirname, "assets", "sammy.png");
console.log("png file path is: ", pngFile);
console.log("is this file exists ? " + fs.existsSync(pngFile));

在开发环境中运行没有任何问题:

$ npx -y tsx index.ts
png file path is:  ***/assets/sammy.png
is this file exists ? true

于是使用 ncc 编译一下看看:

$ npx ncc build index.ts
ncc: Version 0.38.1
ncc: Compiling file index.js into CJS
ncc: Using typescript@5.4.3 (local user-provided)
  4kB  dist/index.js
117kB  dist/sammy.png
121kB  [970ms] - ncc 0.38.1

NOTE: 想要使这个效果生效,必须将 tsconfig.json 中的 module 设置为 ESNext,不能使用 CommonJS,否则这个打包是不起作用的,参见 issue: vercel/ncc#829

从输出中我们就可以看到 ncc 已经将图片资源一并打包进去了,并且代码中还正确处理了 path.join 的路径,这样我们在生产环境中就可以正确的调用这个图片了:

$ node dist/index.js
png file path is:  ***/dist/sammy.png
is this file exists ? true

那么用 tsup 看一下呢?

❯ npx -y tsup index.ts --clean
CLI Building entry: index.ts
CLI Using tsconfig: tsconfig.json
CLI tsup v8.0.2
CLI Target: es2016
CLI Cleaning output folder
CJS Build start
CJS dist/index.js 1.47 KB
CJS ⚡️ Build success in 12ms

仅从输出中就可以看出来图片并没有一并打包到 dist/ 目录下,更别提 path.join 的路径处理了,果然运行有问题:

$ node dist/index.js
png file path is:  ***/dist/assets/sammy.png
is this file exists ? false

类似的,其他两个工具也不能处理这种情况。

但是由于 tsup 支持 esbuild 插件,因此我们可以通过 esbuild 插件解决这个问题,比如这个 esbuild-plugin-copy 以及 esbuild-plugin-raw,或者指定 --publicDir 参数来解决 copy assets 问题,但是特别需要处理路径问题,他们是不会帮你去处理相对路径的问题的,使用者必须自己注意这些路径的变化,选择合适的路径保持兼容。

特别要注意 tsup 还提供一种方式解决这个问题,那就是使用 import static assets,参见 issue egoist/tsup#392

也就是使用类似于各种前端框架 (react/angular/svelte/vue 等) 的 import 的方式引入资源,例如:

import pngFile from "./assets/sammy.png";

但是这个语法不能直接在 ts 中使用,import 默认只能导入 node 支持的模块类型,想要导入文件资源需要使用 pattern ambient module 功能,参见 ambient-modules

定义一个 global.d.ts 文件:

declare module "*.png";

然后再调整下代码:

import pngFile from "./assets/sammy.png";

const pngPath = path.join(__dirname, pngFile);
console.log("png file path is: ", pngPath);
console.log("is this file exists ? " + fs.existsSync(pngPath));

之后设置 loader 参数再编译:

$ npx tsup index.ts --loader '.png=file'
CLI Building entry: index.ts
CLI Using tsconfig: tsconfig.json
CLI tsup v8.0.2
CLI Target: es2016
CJS Build start
CJS dist/index.js            1.56 KB
CJS dist/sammy-OT6F4VL7.png 116.87 KB
CJS ⚡️ Build success in 13ms

此时我们看到图片资源已经被正确打包进去了,而且路径也是正确的:

$ node dist/main.js
png file path is:  ***/dist/sammy-OT6F4VL7.png
is this file exists ? true

NOTE: bun 内置也有个 file loader,默认就支持这种 import 的方式,但是它对于路径处理的方式是不同的,它会返回绝对路径,因此不需要 path.join 处理正确的路径,特别需要注意

Monorepo

Turbo-repo 是我们近期准备采用的工具,它是一个 monorepo 管理工具,可以帮助我们更好的管理我们的 monorepo 项目。

Turbo-repo 自带有 task 缓存功能,在重复执行某些 task 的时候可以加速。

而且 task 之间还可以进行依赖管理,可以很方便的进行项目内的 CI 编排。

觉得有帮助的话,不妨考虑购买付费文章来支持我们 🙂 :

付费文章

友情链接


相关文章