總結
在沒有使用其他套件的情況下從零開始建立一個 react monorepo 專案,此篇筆記會記錄如何調整 package.json 與 webpack 設定來讓專案可以順利編譯。
專案結構可參考:tzynwang/monorepo-study
版本與環境
react: 18.2.0react-dom: 18.2.0webpack: 5.74.0yarn: 1.22.18筆記
package.json
根目錄
// 僅列出 monorepo 必要的部份{ "name": "monorepo-study", "private": true, "scripts": { "start": "yarn workspace @monorepo-study/app start", "build": "yarn workspace @monorepo-study/app build", "del:node_modules": "find . -type d -name 'node_modules' -exec rm -rf {} +" }, "workspaces": ["app/**", "packages/**"]}根目錄的 package.json 指定整個專案的名稱為 monorepo-study,並設定 workspaces 需涵蓋 <root>/app 與 <root>/packages 這兩個資料夾的內容。
要在專案根目錄執行 workspaces 套件的腳本,加上 workspace 關鍵字即可;參考上述範例,在終端執行 yarn start 時,實際執行的是 @monorepo-study/app 這個套件的 yarn start
workspace 下的套件
// <root>/packages/components 資料夾中的 package.json{ "name": "@monorepo-study/components", "main": "./src/index.tsx", "types": "./src/types.d.ts", "version": "1.0.0", "description": "", "keywords": [], "author": "", "license": "MIT", "dependencies": { "@types/react": "^18.0.21", "@types/react-dom": "^18.0.6", "classnames": "^2.3.2", "react": "^18.2.0", "react-dom": "^18.2.0", "typescript": "^4.8.4" }}// <root>/app 資料夾中的 package.json{ "name": "@monorepo-study/app", "version": "1.0.0", "description": "", "scripts": { "start": "node script/start.js", "build": "node script/build.js" }, "keywords": [], "author": "", "license": "MIT", "dependencies": { "@babel/core": "^7.19.3", "@babel/preset-env": "^7.19.3", "@babel/preset-react": "^7.18.6", "@types/react": "^18.0.21", "@types/react-dom": "^18.0.6", "babel-loader": "^8.2.5", "classnames": "^2.3.2", "copy-webpack-plugin": "^11.0.0", "css-loader": "^6.7.1", "dotenv": "^16.0.3", "dotenv-webpack": "^8.0.1", "html-webpack-plugin": "^5.5.0", "mini-css-extract-plugin": "2.6.1", "react": "^18.2.0", "react-dom": "^18.2.0", "style-loader": "^3.3.1", "ts-loader": "^9.4.1", "typescript": "^4.8.4", "webpack": "^5.74.0", "webpack-cli": "^4.10.0", "webpack-dev-server": "^4.11.1" }}注意 workspaces 套件的名稱都需加上在根目錄 package.json 設定好的專案名;比如 packages/components 在 package.json 中的 name 要設定為 @monorepo-study/components(而不是單純的 components)
而開發需要的 dependencies 也都根據 @monorepo-study/components 以及 @monorepo-study/app 各自的需求列出。兩個套件都有安裝 react 與 react-dom,但只有 @monorepo-study/app 安裝了 webpack 與相關套件來處理轉譯、打包需求。
設定好 workspaces 們的 dependencies 後,直接在專案根目錄位置執行 yarn 來執行 dependencies 安裝即可。
webpack
最大的關鍵是在 module.rules.loader 加上 require.resolve 來解析 plugin 位置,其他設定則根據 development 以及 production 環境的需求來處理即可。
以 @monorepo-study/app 的設定為例,只有在 production 環境時才選擇載入 miniCssExtractPlugin,module.rules 中關於 css 檔案的解析,也根據環境是 development 或 production 來決定要採用 miniCssExtractPlugin.loader 或 style-loader 來處理檔案。
差異是:miniCssExtractPlugin 會把樣式獨立輸出為一個 .css 檔案,而 style-loader 則是把樣式直接注入到 DOM 上(如果 injectType 使用預設的 styleTag),在開發時透過 style-loader 可以直接反應修改後儲存的內容,比較方便。
而 output.path 則透過 path.resolve 來指定最終輸出的 dist 資料夾要放在專案根目錄中。
const path = require("path");const dotenvWebpack = require("dotenv-webpack");const htmlWebpackPlugin = require("html-webpack-plugin");const miniCssExtractPlugin = require("mini-css-extract-plugin");require("dotenv").config();
module.exports = function (webpackEnv) { const isProduction = webpackEnv === "production"; return { entry: "./src/index.tsx", output: { clean: true, chunkFilename: "js/[name].[contenthash:8].chunk.js", filename: "js/[name].[contenthash:8].js", path: path.resolve(__dirname, "..", "..", "dist"), // export final bundle to root/dist folder }, plugins: [ new dotenvWebpack(), new htmlWebpackPlugin({ template: "./public/index.html", }), isProduction && new miniCssExtractPlugin({ filename: "css/[name].[contenthash:8].css", chunkFilename: "css/[name].[contenthash:8].chunk.css", }), ].filter(Boolean), module: { rules: [ { test: /\.tsx?$/, exclude: /node_modules/, loader: require.resolve("ts-loader"), }, { test: /\.js$/, exclude: /node_modules/, loader: require.resolve("babel-loader"), options: { presets: [ require.resolve("@babel/preset-env"), require.resolve("@babel/preset-react"), ], }, }, { test: /\.module\.css$/, use: [ isProduction ? { loader: miniCssExtractPlugin.loader } : { loader: require.resolve("style-loader") }, { loader: require.resolve("css-loader"), options: { importLoaders: 1, modules: { localIdentName: "[path][name]__[local]__[hash:base64:5]", }, }, }, ], }, { test: /\.css$/, exclude: /\.module\.css$/, use: [ isProduction ? miniCssExtractPlugin.loader : require.resolve("style-loader"), require.resolve("css-loader"), ], }, ], }, resolve: { extensions: [".tsx", ".ts", ".jsx", ".js"], }, };};.gitignore
不需要加上 / 或 **,直接輸入 node_modules 與 dist 即可直接忽略在專案各層有著同樣名稱的資料夾