webpack(v5.7)+React(v18.0)+react-router(v6.3)+Mobx(v6.5)+TS(v4.6)從零開始構建

語言: CN / TW / HK

theme: channing-cyan highlight: atelier-dune-dark


萬丈高樓平地起

先找塊地,挖個坑 - 進入專案根目錄,執行npm init -y生成初始的package.json檔案,並將其配置項的入口main刪除,新增private:true。 - 執行yarn add -D webpack webpack-cli webpack-merge安裝webpack相關的東東。 - 在根目錄下建立.gitignore檔案,其內用來忽略一些我們不需要提交到遠端的目錄或檔案。

webpack的配置

坑已挖好,開始打地基

坑裡邊打鋼筋柱子

  • 在根目錄下建立build目錄,用來存放我們後期用於打包的一些配置檔案。
  • build目錄下建立webpack.config.js檔案,寫入基本的配置,如下:
  • 在根目錄下建立src目錄,在其內建立index.html模板檔案。同時建立index.tsx入口檔案 js const path = require('path'); module.exports = { entry: { app: path.join(__dirname, '../src/index.tsx') }, output: { path: path.join(__dirname, '../dist'), filename: '[name].[chunkhash:8].bundle.js', // publicPath: "/", chunkFilename: 'chunk/[name].[chunkhash:8].js', }, resolve: { // 解析這些檔案 extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'] }, }

配置babel-loader解析resolve.extensions中的多種型別檔案

  • 安裝yarn add -D @babel/core bable-loader @babel/preset-env
  • Babel 的核心功能包含在 @babel/core 模組中,必須安裝

外掛和預設

  • 程式碼轉換功能以外掛的形式出現,外掛是小型的 JavaScript 程式,用於指導 Babel 如何對程式碼進行轉換。
  • @babel/preset-env可以讓你使用最新的javaScript。
  • @babel/preset-react用來轉換JSX片段。
  • bable-loader使用 Babel 載入 ES2015+ 程式碼並將其轉換為 ES5。
  • babel 會在每個檔案都插入輔助程式碼,這就會使得程式碼體積過大。可以使用Babel runtime作為一個獨立模組,來避免重複引入一些不需要的程式碼。你必須執行yarn add -D @babel/plugin-transform-runtime來把它包含到你的專案中,然後使用yarn add @babel/runtime@babel/runtime安裝為一個依賴。 js module.exports = { module: { // 將缺失的匯出提示成錯誤而不是警告 strictExportPresence: true, // loaders rules: [{ test: /\.(js|mjs|jsx)$/, include: path.join(__dirname, 'src'), use: { // 當有設定cacheDirectory時,指定的目錄將用來快取 loader 的執行結果。 loader: 'babel-loader?cacheDirectory', options: { presets: ['@babel/preset-env', "@babel/preset-react"], // 'transform-runtime' 外掛告訴 Babel,要引用runtime來代替注入 // 引入 @babel/plugin-transform-runtime 並且使所有輔助程式碼從這裡引用。 plugins: ['@babel/plugin-transform-runtime'], } } },{ test: /\.(ts|tsx)$/, include: path.join(__dirname, 'src'), use: { loader: 'ts-loader', } }] }, }

新增TS支援

  • npm install --save typescript awesome-typescript-loader source-map-loader
  • awesome-typescript-loader可以讓Webpack使用TypeScript的標準配置檔案 tsconfig.json編譯TypeScript程式碼。
  • source-map-loader使用TypeScript輸出的sourcemap檔案來告訴webpack何時生成 自己的sourcemaps。方便我們除錯。
  • 在根目錄下建立tsconfig.json檔案。配置如下: js { "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": false, // 允許export=匯出,由import from 匯入 "esModuleInterop": true, // 允許從沒有設定預設匯出的模組中預設匯入。這並不影響程式碼的輸出,僅為了型別檢查。 "allowSyntheticDefaultImports": true, "removeComments": false, // 刪除註釋 "alwaysStrict": true, // 在程式碼中注入'use strict' // 禁止對同一個檔案的不一致的引用 "forceConsistentCasingInFileNames": true, "module": "ESNext", "moduleResolution": "Node", "noUnusedLocals": true, // 檢查只宣告、未使用的區域性變數(只提示不報錯) "importHelpers": true, // 通過tslib引入helper函式,檔案必須是模組 "experimentalDecorators": true, // 啟用實驗性的ES裝飾器。 "resolveJsonModule": true, //是否允許把json檔案當做模組進行解析 "isolatedModules": true, "noEmit": true, "jsx": "react-jsx" }, "include": [ "src", "*.d.ts"//配置的.d.ts檔案,用於定義一些 declare ], }

新增符合ts的路徑別名

  • build目錄下,建立alias.js檔案,用於書寫我們使用到的路徑別名。 ```js const path = require('path')

module.exports = { "@img": path.resolve(__dirname, '../src/assets/images'), "@svg": path.resolve(__dirname, '../src/assets/svg'), "@pages": path.resolve(__dirname, '../src/pages'), "@store": path.resolve(__dirname, '../src/store'), "@components": path.resolve(__dirname, '../src/components'), "@routes": path.resolve(__dirname, '../src/routes'), } - 然後將其配置在webpack的resolve選項下的alias屬性中。 - 接著,在專案根目錄下建立`paths.json`檔案,用於配置ts的路徑別名。內容如下:js { "compilerOptions": { "baseUrl": ".", "paths": { "@/": [ "" ], "@src/": [ "src/" ], "@pages/": [ "src/pages/" ], "@components/": [ "src/components/" ], "@routes/": [ "src/routes/" ], "@assets/": [ "src/assets/" ], "@store/": [ "src/store/" ] } } } `` - 在tsconfig.json檔案中使用extends配置項繼承上面的paths.json`檔案。 - 大功告成。

配置html模板

  • 安裝 yarn add -D html-webpack-plugin
  • 該外掛將為你生成一個 HTML5 檔案, 在 body 中使用 script 標籤引入你所有 webpack 生成的 bundle。 只需新增該外掛到你的 webpack 配置中。如下:
  • 在根目錄下建立src目錄,在其內建立index.html模板檔案。同時建立index.tsx入口檔案 js const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { plugins: [ new HtmlWebpackPlugin({ template: `${path.join(__dirname, "../src")}/index.html` }) ], }

配置本地開發服務以及熱更新

  • 安裝 yarn add -D webpack-dev-server
  • 利用webpack-dev-server搭建本地的web server,並啟用模組熱更新 ```js

module.exports = { mode: 'development', devServer: { client: { // 當出現編譯錯誤或警告時,在瀏覽器中顯示全屏覆蓋。 overlay: true, // 在瀏覽器中以百分比顯示編譯進度。 progress: false, // 告訴 dev-server 它應該嘗試重新連線客戶端的次數。當為 true 時,它將無限次嘗試重新連線。 reconnect: true, }, // 啟用gzip 壓縮 compress: true, port: 9000, // 啟用webpack的模組熱替換 hot: true, // 告訴 dev-server 在伺服器已經啟動後開啟預設的瀏覽器 open: true, // 代理---處理跨域轉發 proxy: {

    }
},

} `` - 在package.json檔案的scripts選項中新增"dev": "webpack-dev-server --config build/webpack.config.js"- 接著我們執行yarn dev`命令,在服務啟動之後就可以看到會自動開啟一個瀏覽器視窗,顯示我們index.tsx檔案中的內容。

配置樣式loader

  • yarn add -D less less-loader style-loader css-loader postcss-loader postcss
  • 再安裝一些postcss需要的外掛yarn add -D postcss-flexbugs-fixes postcss-preset-env postcss-normalize 配置如下:(後面再做公共程式碼的提取) js const cssRegex = /\.css$/; const cssModuleRegex = /\.module\.css$/; const lessRegex = /\.(less)$/; const lessModuleRegex = /\.module\.(less)$/; module.exports = { // ... module: { // ... rules: [ // ... // 配置css-loader { test: cssRegex, exclude: cssModuleRegex, use: [ // style-loader將css插入到DOM中, 推薦將 style-loader 與 css-loader 一起使用 require.resolve('style-loader'), // css-loader 會對 @import 和 url() 進行處理,就像 js 解析 import/require() 一樣。 { loader: 'css-loader', options: { // 啟用/禁用@import規則進行處理,控制@import的解析,預設值為true import: true, // importLoaders 選項允許你配置在 css-loader 之前有多少 loader 應用於 @imported 資源與 CSS 模組/ICSS 匯入。 importLoaders: 1, // 啟用/禁用css模組或者icss及其配置 modules: { mode: 'icss', // 允許配置生成的本地識別符號(ident) // 建議:開發環境使用 '[path][name]__[local]' 生產環境使用 '[hash:base64]' localIdentName: "[path][name]__[local]--[hash:base64:5]", // 允許為本地識別符號名稱重新定義基本的 loader 上下文。 localIdentContext: path.resolve(__dirname, "src"), }, sourceMap: true, }, }, // 自動新增css瀏覽器字首 { loader: require.resolve('postcss-loader'), options: { postcssOptions: { ident: 'postcss', config: false, plugins: [ 'postcss-flexbugs-fixes', // 此外掛用來修復flexbug的問題 [ 'postcss-preset-env', // 包含了autoprefixer { autoprefixer: { flexbox: 'no-2009', }, // 規定按照哪個階段的css來實現polyfill stage: 3, // 預設啟用階段2的功能 }, ], 'postcss-normalize', // PostCSS歸一化,可讓您使用formantize.css或Sanitize.css的各個部分。 ] }, sourceMap: true, }, }, ], // 注意,所有匯入檔案都會受到 tree shaking 的影響。 // 這意味著,如果在專案中使用類似 css-loader 並 import 一個 CSS 檔案, // 則需要將其新增到 side effect 列表中,以免在生產模式中無意中將它刪除: sideEffects: true, }, // 處理.module.css檔案 { test: cssModuleRegex, use: [ require.resolve('style-loader'), { loader: 'css-loader', options: { // 啟用/禁用@import規則進行處理,控制@import的解析,預設值為true import: true, // importLoaders 選項允許你配置在 css-loader 之前有多少 loader 應用於 @imported 資源與 CSS 模組/ICSS 匯入。 importLoaders: 1, // 啟用/禁用css模組或者icss及其配置 modules: { mode: 'local', // 允許配置生成的本地識別符號(ident) // 建議:開發環境使用 '[path][name]__[local]' 生產環境使用 '[hash:base64]' localIdentName: "[path][name]__[local]--[hash:base64:5]", // 允許為本地識別符號名稱重新定義基本的 loader 上下文。 localIdentContext: path.resolve(__dirname, "src"), }, sourceMap: true, }, }, // 自動新增css瀏覽器字首 { loader: require.resolve('postcss-loader'), options: { postcssOptions: { ident: 'postcss', config: false, plugins: [ 'postcss-flexbugs-fixes', // 此外掛用來修復flexbug的問題 [ 'postcss-preset-env', // 包含了autoprefixer { autoprefixer: { flexbox: 'no-2009', }, // 規定按照哪個階段的css來實現polyfill stage: 3, // 預設啟用階段2的功能 }, ], 'postcss-normalize', // PostCSS歸一化,可讓您使用formantize.css或Sanitize.css的各個部分。 ] }, sourceMap: true, }, }, ] }, // 配置支援less { test: lessRegex, exclude: lessModuleRegex, use: [ // style-loader將css插入到DOM中, 推薦將 style-loader 與 css-loader 一起使用 require.resolve('style-loader'), // css-loader 會對 @import 和 url() 進行處理,就像 js 解析 import/require() 一樣。 { loader: 'css-loader', options: { // 啟用/禁用@import規則進行處理,控制@import的解析,預設值為true import: true, // importLoaders 選項允許你配置在 css-loader 之前有多少 loader 應用於 @imported 資源與 CSS 模組/ICSS 匯入。 importLoaders: 3, // 啟用/禁用css模組或者icss及其配置 modules: { mode: 'icss', // 允許配置生成的本地識別符號(ident) // 建議:開發環境使用 '[path][name]__[local]' 生產環境使用 '[hash:base64]' localIdentName: "[path][name]__[local]--[hash:base64:5]", // 允許為本地識別符號名稱重新定義基本的 loader 上下文。 localIdentContext: path.resolve(__dirname, "src"), }, sourceMap: true, }, }, // 自動新增css瀏覽器字首 { loader: require.resolve('postcss-loader'), options: { postcssOptions: { ident: 'postcss', config: false, plugins: [ 'postcss-flexbugs-fixes', // 此外掛用來修復flexbug的問題 [ 'postcss-preset-env', // 包含了autoprefixer { autoprefixer: { flexbox: 'no-2009', }, // 規定按照哪個階段的css來實現polyfill stage: 3, // 預設啟用階段2的功能 }, ], 'postcss-normalize', // PostCSS歸一化,可讓您使用formantize.css或Sanitize.css的各個部分。 ] }, sourceMap: true, }, }, 'less-loader', ], sideEffects: true, }, // 支援.module.less檔案 { test: lessModuleRegex, use: [ require.resolve('style-loader'), { loader: 'css-loader', options: { // 啟用/禁用@import規則進行處理,控制@import的解析,預設值為true import: true, // importLoaders 選項允許你配置在 css-loader 之前有多少 loader 應用於 @imported 資源與 CSS 模組/ICSS 匯入。 importLoaders: 3, // 啟用/禁用css模組或者icss及其配置 modules: { mode: 'local', // 允許配置生成的本地識別符號(ident) // 建議:開發環境使用 '[path][name]__[local]' 生產環境使用 '[hash:base64]' localIdentName: "[path][name]__[local]--[hash:base64:5]", // 允許為本地識別符號名稱重新定義基本的 loader 上下文。 localIdentContext: path.resolve(__dirname, "src"), }, sourceMap: true, }, }, // 自動新增css瀏覽器字首 { loader: require.resolve('postcss-loader'), options: { postcssOptions: { ident: 'postcss', config: false, plugins: [ 'postcss-flexbugs-fixes', // 此外掛用來修復flexbug的問題 [ 'postcss-preset-env', // 包含了autoprefixer { autoprefixer: { flexbox: 'no-2009', }, // 規定按照哪個階段的css來實現polyfill stage: 3, // 預設啟用階段2的功能 }, ], 'postcss-normalize', // PostCSS歸一化,可讓您使用formantize.css或Sanitize.css的各個部分。 ] }, sourceMap: true, }, }, 'less-loader' ], } ] } } tree shaking 是一個術語,通常用於描述移除 JavaScript 上下文中的未引用程式碼(dead-code)。它依賴於 ES2015 模組語法的 靜態結構 特性

抽取css檔案(MiniCssExtractPlugin)建議用於生產環境

  • MiniCssExtractPlugin外掛會將 CSS 提取到單獨的檔案中,為每個包含 CSS 的 JS 檔案建立一個 CSS 檔案,並且支援 CSS 和 SourceMaps 的按需載入。
  • 本外掛基於 webpack v5 的新特性構建,並且需要 webpack 5 才能正常工作。
  • 與 extract-text-webpack-plugin 相比:
    • 非同步載入
    • 沒有重複的編譯(效能)
    • 更容易使用
    • 特別針對 CSS 開發
  • 建議 mini-css-extract-plugin 與 css-loader一起使用。

提取公共的樣式loader處理

  • build目錄下新建rules目錄同時建立子檔案styleRules.js,用於存放webpack配置項module中的一些rules規則配置。
  • 同時我們將之前的對js、ts等檔案的處理loader也抽取到名為jsRules.js的檔案中。

image.png

image.png 具體詳細程式碼檢視

處理圖片、字型檔案等資源

  • 資源模組(asset module)是一種模組型別,它允許使用資原始檔(字型,圖示等)而無需配置額外 loader。
  • 在 webpack 5 之前,通常使用:
  • raw-loader 將檔案匯入為字串
  • url-loader 將檔案作為 data URI 內聯到 bundle 中
  • file-loader 將檔案傳送到輸出目錄
  • 資源模組型別(asset module type),通過新增 4 種新的模組型別,來替換所有這些 loader:
    • asset/resource 傳送一個單獨的檔案並匯出 URL。之前通過使用 file-loader 實現。
    • asset/inline 匯出一個資源的 data URI。之前通過使用 url-loader 實現。
    • asset/source 匯出資源的原始碼。之前通過使用 raw-loader 實現。
    • asset 在匯出一個 data URI 和傳送一個單獨的檔案之間自動選擇。之前通過使用 url-loader,並且配置資源體積限制實現。 此外,當在 webpack 5 中使用舊的 assets loader(如 file-loader/url-loader/raw-loader 等)和 asset 模組時,你可能想停止當前 asset 模組的處理,並再次啟動處理,這可能會導致 asset 重複,你可以通過將 asset 模組的型別設定為 'javascript/auto' 來解決。 此處,我們使用type模組型別為asset,讓其自動處理: js module: { rules: [ { test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/], type: 'asset', parser: { dataUrlCondition: { maxSize: 10000, }, }, }, { test: /\.svg$/, use: [ { // @svgr/webpack允許將svg作為元件使用 loader: require.resolve('@svgr/webpack'), options: { prettier: false, svgo: false, svgoConfig: { plugins: [{ removeViewBox: false }], }, titleProp: true, ref: true, }, }, { loader: require.resolve('file-loader'), options: { name: 'static/media/[name].[hash].[ext]', }, }, ], // 一個條件,用來與被髮出的 request 對應的模組項匹配。 issuer: { and: [/\.(ts|tsx|js|jsx|md|mdx)$/], }, }, ] } 詳細程式碼檢視

區分webpack打包環境

我們先將webpack分為開發環境、生產環境。 - 首先我們在build目錄下建立constants.js檔案用於存放一些我們經常用到的變數。 ```js // 存放一些用到的常量 const IS_DEV = process.env.NODE_ENV === "development" const APP_ENV = process.env.APP_ENV || "prod" const FILE_EXTENSIONS = [".ts", ".tsx", ".js", "jsx", '.json']

module.exports = { IS_DEV, APP_ENV, FILE_EXTENSIONS } `` -yarn add -D webpack-merge使用webpack-merge來對一些公共的配置和不同環境下的配置進行自動合併。 - 在build目錄下建立webpack.dev.config.js。 - 我們將之前寫好的webpack.config.js修改其內部的程式碼,區分開發環境和生產環境的配置。 - 在webpack.dev.config.js`中新增devServer選項。

打包優化

  • 在build目錄下建立optimization.js檔案,用於書寫打包優化的配置。
  • webpack v5 自帶了terser-webpack-plugin,如果想自定義配置,則仍需要安裝該外掛。
  • terser-webpack-plugin外掛使用terser來壓縮js
  • 使用css-minimizer-webpack-plugin外掛來壓縮css,該外掛內部使用了cssnano來優化和壓縮css。 詳情配置檢視

程式碼規範配置

手摸手教你使用最新版husky(v7.0.1)讓程式碼更優雅規範

配置antd庫 按需引入

  • yarn add antd 並根據官方文件配置即可

配置react路由管理

  • yarn add react-router-dom 此時會安裝v6.3.0版本。
  • import { Outlet,useParams,NavLink, useSearchParams, useLocation } from "react-router-dom" Outlet保證了路由切換時,父級路由頁面的內容會一直存在。
  • <Route path="*"/>僅在路由不存在時才會匹配該路由。
  • useParams用來獲取路由上的引數。
  • NavLink用來改變路由按鈕啟用時的顏色。檢視
  • useSearchParams用來獲取和設定路由上的?後邊的引數。const [searchParams, setSearchParams] = useSearchParams(); 檢視
  • useLocation 自定義路由行為,該鉤子返回一路由資料的物件。具體檢視 具體的react-router-dom v6版本的詳細介紹 請看 或者 查閱官方文件

寫在最後

本篇實戰型文章,會不定時持續的更新,直到專案框架搭建完畢。 歡迎一鍵三連!!! 文中,有不對的地方,或者有更好的寫法的話,歡迎各位在評論區留言指出!!!