從零開(kāi)始使用 webpack 搭建 typescript + react 項(xiàng)目
賽博朋克的動(dòng)畫(huà)真他媽好看,這種反抗現(xiàn)行強(qiáng)權(quán)和秩序的故事,我他媽一輩子都看不膩。

為了熟悉 webpack 相關(guān)工具鏈,考慮跟隨官方文檔,從零開(kāi)始去創(chuàng)建一個(gè) typescript + react 的項(xiàng)目以作為實(shí)踐,其中盡量為所有配置項(xiàng)的配置和意義都給予描述,專(zhuān)注所以然而非其然。這里想達(dá)到下面的目標(biāo):
最少的配置項(xiàng),所有配置項(xiàng)都明確描述,盡量依賴默認(rèn)配置項(xiàng)
支持 typescript,tsx,集成 react
提供和 create-react-app 一樣的體驗(yàn)
啥是 webpack
簡(jiǎn)單來(lái)說(shuō),webpack 是一個(gè)靜態(tài)的打包器,webpack 從特定的 js 文件(稱為 entry)開(kāi)始去構(gòu)建對(duì)應(yīng)的依賴樹(shù),其中包括該 js 文件依賴(import)的文本,css,其它 js 文件等,每個(gè)依賴樹(shù)或 entry 都會(huì)打成一個(gè)包 bundle,也稱為 chunk。
每個(gè)可以 import 的文件稱為 module。
項(xiàng)目創(chuàng)建
首先創(chuàng)建一個(gè)新文件,執(zhí)行npm init
,一路 Enter,創(chuàng)建一個(gè)默認(rèn)的 package.json 文件,然后安裝相應(yīng)依賴。
編輯 package.json,移除 main 屬性,添加 private 屬性為 true。
然后初始化 tscconfig.json:
first step
先研究一下 webpack 的基礎(chǔ)用法,考慮創(chuàng)建文件夾 src,創(chuàng)建兩個(gè) js 文件 index.js,Util.js:
這時(shí)的項(xiàng)目結(jié)構(gòu):
然后執(zhí)行npx webpack
命令,能夠發(fā)現(xiàn)它生成了dist/main.js
,其中內(nèi)容為:
顯然,webpack 將 index.js 和 Util.js 打包成了 main.js 文件,這就是 webpack 的默認(rèn)行為——以src/index.js
為 entry,輸出到dist/main.js
。因?yàn)?index.js 引入了 Util.js,因此 Util.js 被一并打包(并進(jìn)行了優(yōu)化)。這時(shí)候我們就可以在 dist 目錄下添加 index.html 并引入 main.js 以查看效果。
但對(duì)其它文件呢?比如 jsx 文件?考慮編寫(xiě)一個(gè)簡(jiǎn)單的 jsx 文件并將其 import:
再執(zhí)行npx webpack
,webpack 會(huì)試圖找 js,json,wasm 后綴的 Hello 文件,找不到后抱怨Field 'browser' doesn't contain a valid alias configuration
,這是 webpack 的默認(rèn)配置,而這里顯然需要自定義配置。
創(chuàng)建配置文件
在項(xiàng)目根目錄(package.json 同級(jí)目錄)下創(chuàng)建文件 webpack.config.ts(也可以是 js,但這里利用上 ts 的類(lèi)型機(jī)制;webpack 默認(rèn)不支持 ts,需要安裝 ts-loader,ts-node 依賴),這個(gè)文件將是 webpack 的配置文件。
配置 ts 支持
webpack 加載文件是通過(guò) loader 去進(jìn)行的,特定的 loader 加載特定類(lèi)型的文件(畢竟要能夠打包到 js 文件中,因此必須要將這些文件變成 js 代碼格式),比如 raw-loader 加載文本文件,ts-loader 加載 typescript 文件……
在配置中的 module.rule 項(xiàng)便可以配置 loader 以及相應(yīng)的后綴,這里配置 tsx 和 ts:
配置了這項(xiàng)后,便可以去直接導(dǎo)入 ts 和 tsx 文件了,index 文件也可以改成 ts。tsx 文件需要在 tsconfig.json 中添加一項(xiàng)配置(畢竟 typescript 需要知道要的究竟是哪個(gè) tsx 實(shí)現(xiàn)):
配置絕對(duì)路徑導(dǎo)入
使用相對(duì)路徑去 import 可讀性不好,且重構(gòu)的時(shí)候也不方便,實(shí)現(xiàn)一個(gè)絕對(duì)路徑的導(dǎo)入是比較重要的。
絕對(duì)路徑的配置需要配置兩個(gè)部分——webpack 和 tsconfig,前者是為了讓 webpack 去確定路徑,后者為了讓 ts 編譯器確定路徑。這里我們想讓@
去代替 src 目錄,因此我們可以使用形如@/util
的形式去導(dǎo)入。
對(duì)于 webpack,添加 resolve.alias 配置:
對(duì)于 tsconfig,添加下面的配置:
這里的 baseUrl 似乎是默認(rèn)配置,但不給定這個(gè)配置的話 vscode 不會(huì)補(bǔ)全路徑
既讓 webpack 滿意,也能讓 tsc 滿意,目的已經(jīng)達(dá)到了。
需注意,tsc 編譯時(shí)不會(huì)去轉(zhuǎn)換 paths,因此使用 ts-node 去運(yùn)行,或者使用 tsc 編譯后使用 node 去運(yùn)行時(shí)會(huì)報(bào)錯(cuò),這在 webpack 環(huán)境下不是問(wèn)題,但倘若將服務(wù)端的代碼也放在這就會(huì)有問(wèn)題了,解決方案是使用 tsconfig-paths,這里先不研究這個(gè)。
編寫(xiě) react 示例
好玩的地方來(lái)了,在上面的基礎(chǔ)上創(chuàng)建一個(gè) react 示例。
首先需要在 dist 下去創(chuàng)建一個(gè)文件 index.html,編輯其 body,添加一個(gè)根元素并引入 main.js:
然后編寫(xiě) index.tsx 和 App.tsx:
執(zhí)行npx webpack
,在 dist 目錄下執(zhí)行http-server
,訪問(wèn)127.0.0.1:8080
,bingo!
快結(jié)束了,但這里仍有幾個(gè)問(wèn)題:
每次修改源代碼都需要重新編譯,費(fèi)時(shí)費(fèi)力
可能有緩存問(wèn)題(即 main.js 更新,但瀏覽器仍舊用的舊的 main.js)
css 等重要的資源未提供通過(guò) js 的導(dǎo)入方式
未提供方便命令去進(jìn)行編譯,打包等操作
配置 dev server 和 http-plugin
解決問(wèn)題 1,2 需要一個(gè)合適的熱更新機(jī)制,webpack 提供了相應(yīng)機(jī)制,即 webpack-dev-server,其會(huì)監(jiān)測(cè)源代碼的改變并進(jìn)行熱更新。
devServer 在 webpack 配置文件中配置,只需要添加下面的配置即可:
配置此之后,執(zhí)行npx webpack serve
便可啟動(dòng) devServer。
然后是解決緩存問(wèn)題,這里配置輸出 bundle 文件名為[name].[chunkhash].js
,配置一個(gè)插件 HtmlWebpackPlugin,該插件會(huì)自動(dòng)生成 html 文件,其中會(huì)自動(dòng)引入相應(yīng) script 標(biāo)簽:
這里因?yàn)橐獎(jiǎng)?chuàng)建一個(gè)根 div,且保留原有的 header,因此使用一個(gè)模板 html,內(nèi)容為之前的 html 移除 script 標(biāo)簽的內(nèi)容。此時(shí)啟動(dòng)服務(wù)器,查看網(wǎng)頁(yè)的 header,能看到 js 文件被自動(dòng)引入了,且原有的 header,body 被保留了。
導(dǎo)入 css 文件
現(xiàn)在我們希望能夠?qū)?css 文件,但又不想去手動(dòng)編輯 html 文件,如何去操作呢?當(dāng)然仍舊是使用 loader 去實(shí)現(xiàn):
需要注意的是,cssloader 需要兩個(gè) loader,其中執(zhí)行順序?yàn)閺暮蟮角?,即先?zhí)行 css-loader,再執(zhí)行 style-loader。css-loader 的作用是處理 css 文件中的 import,style-loader 則是添加這樣的 js 代碼,即將 css 文件的內(nèi)容在運(yùn)行時(shí)添加到 header。
單這樣問(wèn)題就來(lái)了——css 文件全放到 js 里會(huì)大大增加 js 文件的大小,有什么方法可以規(guī)避的?可以使用 MiniCssExtractPlugin 這個(gè)插件使在編譯時(shí)將文件復(fù)制到目標(biāo)目錄;配置 MiniCssExtractPlugin 需要配置 plugins 以及相應(yīng) loader:
導(dǎo)入圖片資源
導(dǎo)入 css 很容易,但若是想導(dǎo)入如圖片等資源呢?我們可能會(huì)在配置文件中加入相應(yīng) loader,然后這樣使用圖片資源:
但在 typescript 里,這樣會(huì)有一個(gè)問(wèn)題——tsc 不知道從這圖片文件中究竟導(dǎo)出了什么玩意,這需要我們對(duì) tsc 進(jìn)行配置,方式是在源代碼目錄中添加一個(gè) d.ts 文件,并進(jìn)行相關(guān)定義:
然后在配置文件中添加下面的規(guī)則,這里使用了 webpack 5 對(duì)靜態(tài)資源的處理方式 asset module,不需要額外的 loader,原始方法是使用 url-loader 或 file-loader:
配置完畢后,typescript 就能正確識(shí)別導(dǎo)出的類(lèi)型了。
添加啟動(dòng)命令
每次執(zhí)行都輸入一堆東西很麻煩?考慮在 package.json 中添加相應(yīng)命令:
這里只是介紹最簡(jiǎn)單的使用,各種優(yōu)化,開(kāi)發(fā)和生產(chǎn)配置分離,loader 的配置,plugin,webpack 的整個(gè)生命周期,測(cè)試……有需要再去學(xué)習(xí)。
參考資料
使用 webpack 搭建基于 typescript 的 node 開(kāi)發(fā)環(huán)境 https://marxjiao.com/2018/04/10/node-webpack/
Webpack get started https://webpack.js.org/guides/getting-started
Webpack 使用 ts 配置文件 https://webpack.js.org/configuration/configuration-languages/#typescript
Webpack Configurations https://webpack.js.org/configuration/
Stackoverflow using absolute paths in typescript for imports https://stackoverflow.com/questions/55916731/using-absolute-paths-in-typescript-for-imports