网站首页 文章专栏 用 esbuild 一键打包原生 ES 模块
原生ES模块在生产环境部署中面临着缓存失效,HTTP缓存机制与模块化架构间有着本质冲突。所以使用基于现代构建工具esbuild的模块合并与内容哈希化解决方案。通过构建时静态分析与内容指纹生成,实现可预测的强缓存策略,在保持ES模块开发体验的同时,使部署变得更有可靠性及性能。
想象一下,你精心开发了一个基于原生 ES 模块的现代 Web 应用。代码结构清晰,模块分工明确:
// a.js
export class A { /* ... */ }
// b.js
export class B { /* ... */ }
// app.js
import { A } from './a.js';
import { B } from './b.js';
// ... 应用逻辑
在本地开发时一切完美,但一旦部署到生产环境,问题接踵而至:
浏览器对静态资源的缓存策略是一把双刃剑。当你发布新版本时,用户浏览器可能依然加载着旧版本的 a.js 或 b.js,导致新功能无法生效。虽然可以通过添加 ?v=1.0.1 这样的查询参数来强制更新,但手动维护这些版本号既繁琐又容易出错。
每个独立的 ES 模块都会产生一个独立的 HTTP 请求。即使只有几个小文件,往返延迟也会显著影响页面加载速度。一个包含十几个模块的应用,在移动网络环境下可能需要数秒才能完成所有模块的加载。
原生 ES 模块代码以原始形式传输,没有经过任何压缩或优化。注释、空白符和长变量名都在占用宝贵的带宽。
既然浏览器原生支持 ES 模块,我们为什么还要将它们打包到一起?
答案是:获得对缓存和性能的完全控制权。
通过将所有模块合并为单个(或少数几个)文件,我们可以:
在众多打包工具中,我们选择 esbuild,原因很简单:
下面让我们一步步实现解决方案。
在你的项目根目录下执行以下命令:
# 使用 npm
npm install esbuild --save-dev
# 或使用 yarn
yarn add esbuild --dev
如果只是想快速尝试,也可以直接使用 npx,无需安装:
npx esbuild app.js --bundle --minify --outfile=bundle.js
在 package.json 中添加构建脚本:
{
"scripts": {
"build": "esbuild app.js --bundle --minify --outfile=dist/bundle.js",
"build:watch": "esbuild app.js --bundle --minify --outfile=dist/bundle.js --watch"
}
}
现在只需运行 npm run build 即可完成打包。
对于更复杂的项目,可以创建一个 build.js 配置文件:
// build.js
const esbuild = require('esbuild');
esbuild.build({
entryPoints: ['app.js'],
bundle: true,
minify: true,
sourcemap: true,
outfile: 'dist/bundle.js',
target: ['es2020'], // 指定目标环境
define: {
'process.env.NODE_ENV': '"production"'
}
}).then(() => {
console.log('✅ 打包完成!');
}).catch(() => process.exit(1));
运行此脚本:
node build.js
最后,将原来引用多个模块的 HTML 更新为引用单个打包文件:
<!-- 替换之前的: -->
<!-- <script type="module" src="app.js"></script> -->
<!-- 改为: -->
<script src="dist/bundle.js"></script>
让我们通过一个简单的表格来对比打包前后的变化:
| 指标 | 打包前 | 打包后 | 改善 |
|---|---|---|---|
| 请求数量 | 3+(每个模块一个) | 1 | 减少 70%+ |
| 缓存控制 | 每个文件独立 | 统一控制 | 精确可靠 |
| 传输大小 | 各文件原始大小总和 | 合并+压缩后大小 | 减少 40-60% |
| 首屏加载 | 依赖请求瀑布流 | 单次请求 | 显著加快 |
假设我们有三个文件:
// 打包前:需 3 个请求
import { A } from './a.js'; // 15KB
import { B } from './b.js'; // 8KB
import { utils } from './utils.js'; // 12KB
// 总计:35KB,3个请求
// 打包后:1个请求,单个文件约22KB(经过压缩)
// 内容已合并、优化
通过 esbuild 打包后,我们不仅获得了单个文件,还享受到了以下额外优化:
利用打包的优势,我们可以实现完美的缓存策略:
<script src="/dist/bundle.abc123.js"></script>
通过在文件名中嵌入内容哈希(esbuild 支持 --entry-names=[name]-[hash] 选项),任何代码变更都会生成全新的文件名,从而:
