構建工具和腳手架:從源碼到dist
- **1. 為什么需要工程轉換?**
- **2. 構建工具的核心職責**
- 為什么要求轉換
- **1)明確三種關鍵問題**
- **(2)Webpack 的打包機制**
- **3. 開發服務器(Webpack Dev Server)**
- **4. 文件指紋與源碼地圖**
- **(1)文件指紋(Hash)**
- **2)源碼地圖(Source Map)**
- **5. 腳手架:工程化的最后一環**
1. 為什么需要工程轉換?
-
開發環境 vs. 運行時環境的不一致性
- 開發時:使用現代語法(JSX/Sass/ESM)、npm 管理依賴
- 運行時:瀏覽器僅支持標準 JS/CSS,無法直接識別
node_modules
- 構建工具的作用:將開發環境的代碼轉換成瀏覽器可執行的代碼
-
工程結構的變化
-
開發時:模塊化、依賴關系清晰(
import/require
) -
運行時:扁平化靜態資源(
dist
目錄,含index.html
+bundle.js
)
-
工程的轉換,命令是 **npm run build** 進行打包,打包完成會生成一個 dist 目錄,這個目錄里面有個 html 文件,有個 js 有 css,還有 assets 文件,這就是轉換的結果。
2. 構建工具的核心職責
為什么要求轉換
思考一個問題,我們為什么要去轉換? 是因為我們開發和維護的代碼和運行時需要的代碼不一致了。
我們使用一個語言新的特性,不想去考慮兼容問題,但是運行時不能不考慮,運行時的代碼就希望兼容性更好。再比如說,開發和維護的代碼希望使用一些非常簡便的語法,像 jsx,sass,我們需要對語言進行增強,這個寫起來更舒服,生產效率更高,但是這個運行時它不支持,而運行時需要的代碼是什么呢,是一個非常純粹滿足語言標準的代碼,沒有那些花里胡哨的代碼。
因為開發和維護的代碼和運行時的代碼不一致,所以我們需要一個東西去轉換,這是對代碼層面的。同樣,工程層面也是一個道理,我們開發和維護的工程和運行時的工程不一樣。我們開發和維護的時候希望可以使用 npm 去安裝各種第三方庫,生成 node_modules,但是運行時不行,瀏覽器環境并不支持 npm ,所以我們運行時的工程是非常傳統的,也就是說打包之后的代碼它就完全脫離了開發的環境。我們打包之后的代碼跟傳統代碼一樣通過index.html 右鍵在瀏覽器上運行。
我們開發的時候的工程和運行時的工程結構不一致了,所以我們就需要找一個東西來進行這個轉換,而進行轉換的工具就叫做構建工具。所以構建工具是用來進行工程的轉換的。
1)明確三種關鍵問題
-
哪種工程更適合開發和維護?
- Webpack:一切皆模塊(JS/CSS/圖片均可
import
)
- Webpack:一切皆模塊(JS/CSS/圖片均可
-
哪種工程更適合運行時?
- 傳統 HTML+JS+CSS 結構,可直接在瀏覽器運行
-
如何轉換(打包)?
- 依賴分析 → 代碼轉換(Babel/Loader)→ 合并優化
這三個問題沒有標準,這就造成了在不同的需求下,從不同的角度出發,著力點不一樣,這三個點的理解可能就不一樣,于是就造成了各種構建工具的差異。構建工具有很多,webpack、rollup、esbuild等等,這些構建工具的本質差異是什么,其實就是對上邊這三點的理解不一致。
webpack的這三個東西
- 哪種工程更適合開發和維護 (一切皆為模塊,都可以進行導入)
- 哪種工程更適合運行時 (傳統工程,就是最開始的html右鍵瀏覽器運行)
- 如何轉換(打包) (以一個文件為入口點出發,去尋找他們的依賴關系,導入誰就依賴誰,然后就形成了一大堆文件,最后進行合并,把所有的 JS 文件合并在一起,把所有的 CSS 文件合并在一起,less 代碼該轉換轉換,資源文件就單獨形成各自的文件)
(2)Webpack 的打包機制
-
依賴分析(不運行代碼,而是解析 AST)
-
支持
ESM (import)
和CJS (require)
-
模塊查找規則:
./
或../
→ 相對路徑- 非
./
開頭 →node_modules
查找(遵循package.json
的main
字段)
-
-
打包結果的特點
- 無模塊化語法(
import/require
被替換) - 合并 JS/CSS(減少 HTTP 請求)
- 文件指紋(Hash 值,優化緩存)
weboack 的入口,在分析依賴關系的時候,具體是如何分析的呢?它分析的方式并不是去運行這個代碼,而是把整個代碼看成是一個字符串,webpack 是不會去運行代碼的,它就是來進行打包轉換的,把入口文件告訴 webpack,會把這個文件的內容讀出來,讀出來過后分析一下這個文件用到了哪些其他的文件。
它是怎么知道用到了哪些其他的文件的,它就是把整個代碼看成一個字符串,然后把這個字符串分解成為一個 AST(抽象語法樹),然后通過抽象語法樹去找到那些導入的語句,而且 webpack 是同時支持 ESM 和 CMJ 的,這就意味著在代碼中即可以使用 import 來進行導入,也可以使用 require 來進行導入,它都支持。
這就解決了一個疑惑,說瀏覽器環境并不支持 CMJ,不能使用 require,那么為什么在 VUE 和 React 代碼里邊可以使用這個 require,是因為寫的 require、import 壓根不是給瀏覽器看的,是給構建工具看的,像 webpack 它來識別導入語句,因為它兩者都支持,所以兩種導入語句都能寫。
無論寫的哪一種,實際上都是告訴 webpack 這里邊有依賴關系,然后它把依賴關系分析完之后進行打包,打包結果里邊不會包含任何的導入語句。也就是說在源代碼中寫的import、require 在打包結果里邊壓根就不存在了,打包結果里面是不存在任何的模塊化代碼。
到哪里去找這個依賴的文件呢,也就是模塊的查找,在 webpack 中所有都是模塊,哪怕一個圖片都是模塊,到哪里去找這個圖片,去哪找這個 JS 呢? 有模塊的查找規則。
比如說 import ‘./cover’,cover 是一個文件夾,會默認的去找這個文件夾的 index.js 文件,比如說 import $ from ‘jquery’,目錄里面沒有 jquery文件夾,這又是一個查找規則,當給的路徑不以’./‘,’…/'開頭時,這個時候用的是 node 模塊規則,看一下當前目錄有沒有 node_modules,然后在 node_modules 這個目錄里邊去尋找 jquery文件夾,又找到這個文件夾下面的package.json找到 'main’字段對應的文件,然后在這個文件找到對應的 jquery.js 文件。
- 無模塊化語法(
3. 開發服務器(Webpack Dev Server)
-
作用:實時編譯 + 自動刷新
-
運行機制:
- 啟動
express
服務器 - 內存打包(不生成
dist
,直接放在內存) - 監聽文件變化 → 重新編譯 → 通知瀏覽器刷新
- 啟動
現在是可以進行轉換了,但是該怎么運行呢,不能說每寫一行代碼就去運行一個命令 npm run build 把 dist 在新的工程打開把頁面運行出來。能不能一邊寫代碼一邊自動運行,在 webpack 中使用的辦法就是使用開發服務器。
運行命令 npm run serve,運行之后會給地址。點擊打開就運行出來了,就不用再去先打包然后再用 VScode 去打開這個打包結果再運行。
開發服務器(webpack serve)是由 webpack-dev-server(是webpack 的一個庫) 啟動的,webpack-dev-server 里面又依賴了 express 。
當我們運行 webpack serve 的時候,它會利用 webpack-dev-server 啟動一個開發服務器,與此同時它會去進行打包,相當于幫我們運行了一個 npm run build,只不過這次打包是在內存里邊完成,并不會把打包的結果形成文件,在內存中形成打包結果。
然后控制臺會給一個提示,讓去訪問哪一個地址,當訪問地址時就會打開瀏覽器,瀏覽器會自動的出現這個地址,由瀏覽器去訪問開發服務器,于是瀏覽器向開發服務器發送請求,然后開發服務器會從內存中的打包結果中去拿到一個頁面(index.html),把頁面響應給瀏覽器。瀏覽器就可以看到頁面了,瀏覽器拿到頁面之后就要去渲染頁面了,渲染頁面的工程中會去繼續請求 JS、CSS,開發服務器又回去內存中的打包結果取出相應的 JS、CSS相應給瀏覽器,這樣一來瀏覽器就把整個頁面運行出來了。
這就是整個過程,省略了手動的去打包,手動的去運行瀏覽器的過程。
這樣還能實現源碼變化后自動刷新的功能。是因為 webpack serve 還有個功能,它可以監聽文件的變化,當文件發生變化時,它會觸發重新打包,也就是說更改了內存中的打包結果。光更改沒用,還得讓瀏覽器刷新,一刷新就要重新請求,重新請求得重新拿打包結果,就拿到了新的打包結果,相應的就是新的內容。
4. 文件指紋與源碼地圖
(1)文件指紋(Hash)
- 作用:確保內容不變時用緩存,內容變化時立即更新
- 示例:
main.a3b4c5.js
(哈希值隨內容變化)
打包的 js 或 css 名稱奇奇怪怪的字母數字是文件指紋,其實就是哈希值的前幾位,會隨著源碼的內容的變化而變化的。文件指紋既可以保證文件內容沒變時一直使用緩存結果,也可以保證內容變化之后立即使用最新的結果。
2)源碼地圖(Source Map)
- 作用:調試時映射回原始代碼(而非打包后的代碼)
- 文件:
.map
文件(關聯bundle.js
和源碼)
源碼地圖:打包后’.map’后綴的,是可以讓我們更好的去調試,如果沒有源碼地圖,我們去調試時開發者工具打斷點顯示的代碼是打包之后的代碼而不是源碼,源碼地圖讓打包后的代碼和源碼相對應。
5. 腳手架:工程化的最后一環
vue-cli 、 vite 、 cra 、umijs 等等
雖然有了構建工具,這些目錄結構的安排得自己去組織,構建工具里面的配置得一行一行去寫,各種具體的插件得自己去安裝。這時候就希望有一個工具能幫我們把這些給做了,這個工具就是腳手架,腳手架是用來干什么的,就是來搭工程的。
-
為什么需要腳手架?
- 構建工具配置復雜(Webpack 配置、Babel、Loader)
- 項目結構標準化(如
Vue
/React
官方推薦目錄)
-
核心功能:
-
命令行交互(選擇框架、配置項)
-
生成標準化工程模板(預置
webpack.config.js
、babelrc
等)
根據你的選擇,它給你提供一個工程結構,那些依賴幫我們處理好,配置幫我們考慮好
- 集成最佳實踐(如
Vue CLI
默認支持Sass
、Router
)
-