—— 打通規(guī)范、工具鏈與現(xiàn)代架構的任督二脈
0 引言
模塊化的本質是“分而治之 + 明確邊界 + 可組合復用”。
JavaScript 從最初嵌入的“全局腳本”時代一路演進:
- 早期:全局變量橫飛、依賴順序需手動維護;
- 社區(qū)百花:CommonJS、AMD、UMD 各自維優(yōu);
- ESM 標準化:語言層面原生支持、構建工具深度優(yōu)化;
- 遠程/邊端/微前端場景:揭示對加載時機、體積、隔離性的更高要求。
本文按照時間線 + 工程落地雙主線,既講原理也給實戰(zhàn)示例,幫助你把碎片化知識串成系統(tǒng)。
1 原始階段:Script 標簽 + 命名空間
1.1 全局腳本模式
<script src="jquery.js"></script>
<script src="app.js"></script>
缺點:全局污染、依賴順序、人肉維護。
1.2 IIFE / 揭示式模塊模式
const utils = (function () {
function add(a, b) { return a + b }
return { add }
})()
console.log(utils.add(1, 2))
- 通過立即執(zhí)行函數(shù)隔離私有變量。
- 仍依賴全局單例名
utils,易沖突。
1.3 命名沖突與依賴地獄
- 多人協(xié)作時難以保證唯一前綴。
- 依賴鏈一長就出現(xiàn)“誰先加載誰后加載”的 腳本加載地獄。
2 社區(qū)方案:CommonJS、AMD、UMD
| 維度 | CommonJS | AMD | UMD |
|---|---|---|---|
| 加載方式 | 同步(阻塞) | 異步(回調) | 同步 / 異步 |
| 運行環(huán)境 | Node | 瀏覽器 | 通吃 |
| 核心語法 |
require() / module.exports
|
define([], fn) |
內部判斷 module.exports 是否存在 |
| 優(yōu)缺點 | 簡單直觀,但瀏覽器需打包 | 網絡并發(fā)好,回調繁瑣 | 只是一層 |
2.1 CommonJS 示例
// math.js
exports.add = (a, b) => a + b
// app.js
const { add } = require('./math')
console.log(add(3, 4))
-
模塊執(zhí)行一次后即緩存;再次
require直接取緩存。 - 在瀏覽器需 Browserify/Webpack 打成 bundle。
2.2 AMD 示例
define(['jquery'], function ($) {
return function mount() {
$('#root').text('Hello AMD')
}
})
- 以 依賴數(shù)組 聲明,自帶異步加載。
- 為了解決瀏覽器中多個腳本并行加載的問題而誕生,但存在大量回調與配置繁瑣的問題。
2.3 UMD
(function (root, factory) {
if (typeof module === 'object' && module.exports) {
module.exports = factory()
} else if (typeof define === 'function' && define.amd) {
define(factory)
} else {
root.myLib = factory()
}
})(this, function () { /* ... */ })
- 典型“同時支持 Node、AMD、全局”的包裝。
- 本質還是“打補丁”,漸漸被 ESM 取代。
3 標準化新紀元:ESM
3.1 語法速覽
// math.js
export function add(a, b) { return a + b }
export const PI = 3.14
// app.mjs
import { add, PI } from './math.js'
console.log(add(PI, 1))
-
靜態(tài)結構:
import/export需位于頂層,編譯期即可解析依賴圖。
3.2 靜態(tài)優(yōu)勢
- Tree?Shaking:未被引用的導出在打包階段可被刪除;
- 聲明提升:循環(huán)依賴時獲取到是“引用”而非值拷貝。
3.3 瀏覽器原生加載
<script type="module" src="/main.js"></script>
- 默認 延遲執(zhí)行,等同
defer; - 模塊腳本默認啟用 嚴格模式 + CORS。
3.4 Node.js ESM
-
package.json置"type":"module"或使用后綴.mjs。 -
import.meta.url、import.meta.resolve給出運行時信息。
3.5 CommonJS ? ESM 互操作
-
ESM → CJS:
import pkg from 'cjs-lib'實際拿到module.exports; -
CJS → ESM:
require('./esm-file.mjs')會返回一個 Promise。
??注意“默認導出”差異:CJS 只有module.exports一個出口。
3.6 高級用法
動態(tài)導入
button.onclick = async () => {
const { lazy } = await import('./heavy.js')
lazy()
}
支持 代碼分割 + 按需加載。
Top?Level Await
// fetch-data.mjs
const data = await fetch('/api').then(r => r.json())
export default data
使“模塊依賴異步資源”變得自然,但會 阻塞整個依賴圖的評估。
Import Maps(瀏覽器端)
<script type="importmap">
{
"imports": {
"vue": "https://cdn.skypack.dev/vue@3.5.0"
}
}
</script>
允許在瀏覽器端指定模塊路徑映射,簡化依賴管理。目前仍處于實驗階段,需瀏覽器支持。
3.7? 循環(huán)依賴解析
- ESM 先創(chuàng)建“模塊記錄”再執(zhí)行;相互引用時拿到 暫時未賦值的綁定。
4? 構建工具與打包生態(tài)
4.1? Bundler 進化
| 年份 | 工具 | 特征 |
|---|---|---|
| 2011 | Browserify | CommonJS to IIFE |
| 2012 | Webpack | Loader & Plugin 生態(tài)、HMR |
| 2015 | Rollup | ESM 優(yōu)化、極致 tree?shaking |
| 2020 | Vite / ESBuild | Dev Server + ESM 原生 + 極快構建 |
| 2023 | Bun | JS Runtime + Bundler 一體 |
4.2? Tree?Shaking & Scope?Hoisting
- Rollup 靜態(tài)分析導出使用情況;
-
Webpack
sideEffects:false+TerserPlugin; - Scope?Hoisting 把多模塊函數(shù)折疊成一個 IIFE,減少閉包開銷。
4.3? 輸出格式
export default {
format: ['esm', 'cjs', 'umd'],
dts: true
}
- Library 場景通常 多格式發(fā)布;
- App 場景僅需瀏覽器友好的
esm+ polyfill 方案。
4.4? 源映射與 Chunk 策略
-
//# sourceMappingURL=幫助線上錯誤定位到源碼行; -
webpackChunkName注釋或rollup.output.manualChunks手工拆包。
4.5? Monorepo / 多包管理
-
pnpm workspaces+nx/rush實現(xiàn) Hoist 依賴 + 原子發(fā)布。
5? 跑在云端與邊緣的模塊化
| 平臺 | 特征 | 模塊格式 |
|---|---|---|
| Deno | 原生 ESM、URL 導入 | ESM |
| Cloudflare Workers | V8 Isolate + ESM-only | ESM |
| Vercel Edge | 基于 V8 的無服務器 | ESM |
- Service Worker 支持ESM:
new Worker('./worker.js', { type:'module' })。 - HTTP/2?Push、Early Hints 搭配 ESM preload 減少白屏。
6? 微前端與模塊聯(lián)邦
6.1? 需求背景
- 多團隊并行交付;
- 漸進式重構舊系統(tǒng)。
6.2? Webpack Module?Federation
// host webpack.config.js
plugins:[
new ModuleFederationPlugin({
remotes: { shop: 'shop@https://cdn.xxx.com/remoteEntry.js' }
})
]
// remote app
module.exports = {
name:'shop', exposes:{ './Header':'./src/Header.tsx' }
}
運行時拉取遠程 bundle 并注入共享依賴版本。
6.3? CDN + import() 方案
const Header = await import('https://unpkg.com/shop/Header.js')
無打包器耦合,更貼近瀏覽器原生。
6.4? 共享依賴治理
-
singleton: true指定依賴只加載一次; - 版本不匹配降級到 fiber 沙箱或 iframe 隔離。
7? 安全、性能與最佳實踐
-
供應鏈安全
- Subresource?Integrity:
<script src="..." integrity="sha384?..."> - 鎖文件審計:npm?audit / snyk。
- Subresource?Integrity:
-
Side?Effects 字段
-
package.json?里"sideEffects": false協(xié)助 tree?shaking。
-
-
條件導入(Node ≥?20)
import pkg from './lib/index.js' with { type:'json' } -
類型 & Lint
- TypeScript?+?JSDoc 雙保險;
- ESLint
import/no?cycle檢測循環(huán)依賴。
-
性能指標
- 首屏 JS ≤?100?kB、Chunk ≤?50?kB;
- 長任務切分、動態(tài) import 保持主線程流暢。
8? 未來展望
| 提案 | 目標 | 進度 |
|---|---|---|
| Package?Exports/Imports | 更安全的包入口映射 | Stage?4, 已被 Node 采納 |
| Module?Fragments | 內嵌 HTML 模塊化 | Stage?2 |
| WASM Modules | JS ?? Wasm 無縫導入 | 試驗中 |
- 零打包時代:ESBuild/Bun 利用極快編譯 + 瀏覽器原生 ESM。
9? 結語
模塊化是一條“控制復雜度”的主線:
從命名空間到 CommonJS,再到 ESM 與 Module?Federation,每一次演進都在拉高抽象層,降低協(xié)作成本。
理解背后的 依賴解析、作用域隔離、加載策略,你才能在任何規(guī)模的項目中做出最優(yōu)權衡。
至此,你已擁有 JavaScript 模塊化全景地圖。
未來無論構建多包庫、落地微前端,抑或在 Edge Runtime 編寫函數(shù),你都能以最合適的模塊方案駕輕就熟。
希望本文能幫助你更好地理解和應用 JavaScript 模塊化的相關知識。
如有任何問題或建議,歡迎在評論區(qū)交流討論!