在使用 Deno 进行构建时,发现生成的可执行文件体积较大。相比之下,使用 Node.js 构建的项目通常非常小,尤其是在不安装依赖包的情况下,体积通常不到 1MB。然而,Windows 下 Deno 生成的可执行文件体积接近 100 MB,这让我不禁思考:为什么 Deno 打包的产物会如此庞大?是否有可以优化的空间?这种情况让我开始深入研究Deno 的打包方式及其内部结构,希望能够找到一些优化策略或者理解它如此设计的原因。
最后你会发现,这100M包含了所有的资源以及运行时,再也不需要在部署设备安装nodejs或者docker啦
推荐前置阅读: Deno2.0与Bun对比,谁更胜一筹?可能Deno目前更适合serverless业务
Deno 安装
Windows
使用 PowerShell:
irm https://deno.land/install.ps1 | iex
Linux
curl -fsSL https://deno.land/install.sh | sh
更多安装方式请参考 Deno 安装文档
创建项目
- 创建一个干净的 📁 目录(可选)。
- 在目录中创建一个
test.js
文件。 - 编写以下代码,例如:
// 一个简单的应用代码,每隔10秒更新目录下一个文件内的时间戳数据,包含时间读取和文件操作权限
const filePath = `${
Deno.cwd()}/timestamp`;
async function updateTimestamp() {
while (true) {
const timestamp = new Date().toISOString();
try {
await Deno.writeTextFile(filePath, timestamp);
console.log(`Timestamp updated: ${
timestamp}`);
} catch (err) {
console.error(`Failed to update timestamp: ${
err}`);
}
await new Promise(resolve => setTimeout(resolve, 10000));
}
}
updateTimestamp();
构建可执行文件
使用 deno compile
命令进行构建:
deno compile --output test ./test.js
在 Windows 下会生成 test.exe
。
运行可执行文件
方式一:双击执行文件
可以通过双击来运行生成的 test.exe
文件。由于示例代码会持续运行进程,因此可以看到命令窗口。如果是非持续运行的程序,可能只会闪一下。
运行过程中会请求权限:
输入 y
允许权限请求后:
方式二:命令行授权运行
可以通过命令行一次性授权并运行:
.\test.exe --allow-all
构建物分析
构建物的组成
将生成的 test.exe
文件解压后可以看到它的组成部分:
可以看到两个主要部分:.text
(49 MB) 和 .rdata
(29 MB),它们占据了可执行文件的大部分空间。
深入分析
Deno 打包后的可执行文件之所以如此庞大,是因为它包含了以下部分:
- V8 引擎:Deno 基于 V8 引擎来执行 JavaScript 代码,因此 V8 作为 Deno 运行时的一部分被打包在可执行文件中。
- Deno 运行时: Deno 运行时自身也包含了许多功能,如权限管理、模块加载等。这些功能都需要包含在打包的可执行文件中,以确保应用在不同环境中正常运行。
为什么如此设计?
Deno 的设计理念之一是让构建后的可执行文件能够在目标设备上独立运行,无需再额外安装Deno 或其他依赖。这种 "一次构建,到处运行" 的模式使得可执行文件在部署时更加方便,但同时也增加了文件的体积。相比于传统的 Node.js 应用, Deno 将运行时和引擎都集成到可执行文件中,因此文件体积较大。
与 Node.js、Bun 以及 Docker+Node.js 部署的对比
在构建和部署 JavaScript 应用程序时, Deno、Node.js、Bun 以及使用 Docker 打包的 Node.js 应用都有各自的优缺点。下面我们来对比这些不同的工具和方法。
1. Node.js
Node.js 是最广泛使用的 JavaScript 运行时,它依赖于 V8 引擎来执行 JavaScript 代码。使用 Node.js 部署应用程序通常需要先安装 Node.js 环境,然后通过 npm 或 yarn 安装项目的依赖项。Node.js 的优势在于其成熟的生态系统和广泛的社区支持。
然而,Node.js 应用程序部署时需要依赖环境,这意味着必须在目标服务器上安装 Node.js,或者通过容器化的方式来确保环境一致性。此外,Node.js 本身并不直接支持将应用程序打包为单个可执行文件,因此需要其他工具(如 pkg
)来实现类似的效果。
2. Bun
Bun 是一种新兴的 JavaScript 运行时,旨在提供比 Node.js 更快的执行速度和更简单的开发体验。Bun 集成了包管理器 📦、JavaScript 运行时和捆绑工具,旨在简化整个开发流程。与 Node.js 相比,Bun 的包管理速度更快,且在性能上进行了优化。
不过,Bun 目前仍在快速发展中,生态系统相对较小,还没有 Node.js 那样广泛的第三方库支持。此外,Bun 的稳定性和兼容性在某些场景下可能还需要进一步验证。
3. Deno
Deno 的主要特点在于安全性和自包含的可执行文件。与 Node.js 不同, Deno 默认禁止网络、文件系统等敏感操作,除非明确授权,这种权限管理的机制使得应用程序更加安全。此外,Deno 可以将应用程序编译为单个自包含的可执行文件,这在部署时非常方便,无需在目标服务器上安装 Deno 运行时。
然而,正因为 Deno 将运行时和 V8 引擎都打包在可执行文件中,导致生成的文件体积非常大。对于一些对文件体积敏感的场景, Deno 的这种设计可能不是最优选择。
4. Docker + Node.js
使用Docker 容器来部署 Node.js 应用是一种非常流行的方法,尤其是在需要环境隔离和一致性的时候。通过 Docker,可以将应用程序及其所有依赖打包在一个容器中,确保在任何支持Docker 的平台上都可以一致地运行。
使用 Docker 部署的优势在于环境的隔离和可移植性,开发者可以确保在本地测试的应用在生产环境中具有相同的依赖和配置。然而,Docker 容器的镜像体积相对较大,这取决于所使用的基础镜像和应用程序的依赖项。此外,使用Docker 还需要一定的运维技能来管理容器的生命周期。
总结对比
- Node.js:适合需要广泛生态支持的应用,部署时需要环境依赖。
- Bun:更快的包管理和执行速度,但生态系统还不成熟。
- Deno:提供安全性和自包含的可执行文件,部署简单,但文件体积较大。
- Docker + Node.js:提供环境一致性和隔离,适合复杂的生产环境,但镜像体积较大。
根据具体的应用场景和需求,开发者可以选择最适合的工具和方法来进行部署。
可优化的方向
虽然Deno 的可执行文件包含了完整的运行时和引擎,使其体积庞大,但我们可以考虑以下优化方向:
- 裁剪功能:如果应用不需要某些运行时功能,可以探索是否有办法裁剪掉不必要的部分,减少最终可执行文件的体积。
- 压缩工具:使用压缩工具如
UPX
对生成的可执行文件进行压缩,可以有效减小文件体积。 - 模块化设计:在开发时尽可能减少使用第三方库和不必要的模块,保持代码精简,从源头上控制构建物的大小。
结论
Deno 打包的可执行文件体积较大是其设计使然,目的是为了在不同环境中实现独立运行和无依赖的便利性。虽然它的文件体积相比传统 Node.js 应用较大,但可以通过裁剪功能、压缩工具等方式进行一定程度的优化,以适应不同的应用场景需求。
构建成可执行文件后,你的后端应用就可以在没有运行时的设备允许了,尤其是ecs、轻量应用服务器,实现一键部署~