跳至主要内容

[guide] Monorepo with React and TS

這個 template 的功能包含:

  • 編譯 React 元件
  • 支援 TypeScript 的編譯
  • ESLint with TypeScript
  • 可以撰寫測試
  • 透過 lerna 進行版本控制與專案管理
  • 透過 rollup 將專案打包

初始化 lerna

lerna init & lerna create @ pjchender/mono-react-ts-template

$ lerna init

create packages

$ lerna create @mono-react-component-template/core -y
$ lerna create @mono-react-component-template/components -y

Setup TypeScript

setup tsc and build with tsc @ pjchender/mono-react-ts-template

  • 在根目錄建立 tsconfig.settings.json,這支檔案是給 packages/* 中的專案使用的
  • packages/* 中各自建立 tsconfig.json,並且 extends 來自外層的 tsconfig.settings.json
  • 在根目錄建立 tsconfig.json,透過在此檔案中設定 references 來將 packages/* 中的 tsconfig.json 關聯起來
  • 這時候的做法是透過 tsc 來打包專案,但後續會改成使用 babel 搭配 @babel/preset-typescript 來編譯 TypeScript

Add cleanup script

add clean script to remove dist @ pjchender/mono-react-ts-template

每次專案打包後都會產生 dist*.tsbuildinfo 的檔案,透過 rimraf 這個套件來快速移除這些檔案。

  • 安裝 rimraf
  • packages/* 及根目錄的 package.json 建立對應的 clean 指令

Setup ESLint with TypeScript

setup eslint for TS @ pjchender/mono-react-ts-template

  • 安裝和 ESLint、TypeScript 及 React 有關的套件

    npm install -D eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser

    npm install eslint-config-airbnb-typescript \
    eslint-plugin-import@^2.22.0 \
    eslint-plugin-jsx-a11y@^6.3.1 \
    eslint-plugin-react@^7.20.3 \
    eslint-plugin-react-hooks@^4.0.8 \
    @typescript-eslint/eslint-plugin@^4.4.1 \
    eslint-config-prettier \
    eslint-plugin-prettier \
    eslint-plugin-simple-import-sort \
    --save-dev

    npm install --save-dev --save-exact prettier
  • 在根目錄建立 .eslintrc.js,在 packages/* 中的 eslint 會繼承這支檔案的設定

  • packages/* 中建立 .eslintrc.js,並透過 extends 繼承根目錄的 .eslintrc.js

  • 建立 .prettierrc

  • 建立 .eslintignore

  • packages/* 及根目錄的 package.json 建立對應的 lint 指令

Testing (Jest)

setup babel for testing(jest) @ pjchender/mono-react-ts-template

  • 安裝所需的套件

    npm install jest \
    @types/jest \
    eslint-plugin-jest \
    @babel/preset-env \
    @babel/preset-typescript \
    --save-dev
  • 在根目錄建立 babel.config.js,Jest 在執行時預設就會讀這支的設定

  • packages/* 中各自建立 .babelrc 並且繼承根目錄的 babel.config.js

  • packages/* 及根目錄的 package.json 建立對應的 test 指令

rollup

setup rollup with babel to build ts files @ pjchender/mono-react-ts-template

安裝所需的套件

npm install rollup \
@rollup/plugin-node-resolve \
@rollup/plugin-commonjs \
@rollup/plugin-babel \
rollup-plugin-terser \
@babel/plugin-transform-runtime \
--save-dev

編譯 TypeScript

一般來說,最常使用的方式是使用 Babel 來編譯 TypeScript 檔案,搭配 tsc 來檢驗型別並產生 .d.ts 檔案。這裡我們也選擇這樣做。

  • 建立 rollup.config.js

    • @rollup/plugin-node-resolve:在沒有使用 node-resolve 時,rollup 不會去把 node_modules 的內容 resolve 出來並放到 bundle 中,但若使用了 resolve,這時就會把 node_modules 內的結果直接放到 bundle 中。
    • @rollup/plugin-babel:使用 babel
    • @rollup/plugin-commonjs:將 CommonJS 的 modules 轉成 ES6 modules,來讓 rollup 進行 bundle
    • rollup-plugin-terser:將 bundle 的檔案做 minify
  • 由於要讓 Babel 能夠編譯 .ts 檔,需要先透過 node-resolve 找到 .ts 的位置(透過 extensions 的設定)

  • 接著一樣透過 extensions 的設定讓 Babel 能夠處理 ts 檔

  • packages/* 及根目錄的 package.json 建立對應的 build 指令

備註

要選擇使用 Babel 或 TSC 來編譯 TypeScript 的檔案可以參考:Setup TypeScript Template @ pjchender

透過 tsc 產生 type definition

use tsc to build .d.ts files @ pjchender/mono-react-ts-template

由於透過 Babel 編譯 TypeScript 時,並不會產生對應的 .d.ts 檔,此時其他開發者使用此套件時,並不會獲得任何的型別資訊(及限制),因此我們仍需要使用 tsc 來產生 .d.ts

  • 若要使用 tsc 來做型別檢查並產生對應的 .d.ts 檔,記得在 tsconfig.json 中的設定要:

    // tsconfig.json
    // https://www.typescriptlang.org/docs/handbook/babel-with-typescript.html

    "compilerOptions": {
    // Ensure that .d.ts files are created by tsc, but not .js files
    "declaration": true,
    "emitDeclarationOnly": true,
    // Ensure that Babel can safely transpile files in the TypeScript project
    "isolatedModules": true
    }
  • package.json 中要加上 types 這個欄位,讓使用此套件的開發者可以正確載入到 Type Definition

    // package.json
    {
    "name": "@mono-react-component-template/components",
    "version": "0.0.0",
    + "main": "dist/index.cjs.js",
    + "module": "dist/index.esm.js",
    + "types": "dist/index.d.ts",
    "directories": {
    "src": "src",
    "test": "__tests__"
    },
    }
  • packages/* 及根目錄的 package.json 建立對應的 build:ts 指令

設定進版和發佈的指令

setup publish script @ pjchender/mono-react-ts-template

  • 透過 lerna version patch 的指令更新版號,預設的情況下,lerna 會和 git remote 的位址有連動,若不需要,可以加上 --no-git-tag-version
  • 這裡我們會發到由 verdaccio 所建立的本機的 npm registry,因此會把 publish 的 registry 設為 --registry 'http://localhost:4873
diff --git a/package.json b/package.json
index ac740dc..40aa653 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,9 @@
"build:ts": "lerna run build:ts",
"lint": "lerna run lint",
"clean": "lerna run clean",
- "test": "lerna run test"
+ "test": "lerna run test",
+ "version:patch": "lerna version patch --no-git-tag-version",
+ "publish:local": "lerna run build && lerna publish from-package --registry 'http://localhost:4873'"
},
"devDependencies": {
"@babel/plugin-transform-runtime": "^7.14.3",

編譯 React 元件

setup to build react components @ pjchender/mono-react-ts-template

安裝 React 套件

package/components 專案中安裝 react 及相關套件,並且一併放到 peerDependencies 中:

# 由於有在 npm 設定 workspace 所以可以透過 --workspace 進行安裝
npm install -D react @types/react styled-components @types/styled-components --workspace=packages/components

安裝 @babel/preset-react

在還沒有安裝對應的 babel plugin 就要編譯 react 元件時,因為 rollup 不認得 JSX,因此會發生錯誤:

rollup bundle jsx error

npm install -D @babel/preset-react

並在 babel.config.js 中建立對應的設定檔:

diff --git a/babel.config.js b/babel.config.js
index 97ed664..406d237 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -8,6 +8,12 @@ module.exports = {
},
},
],
+ [
+ '@babel/preset-react',
+ {
+ runtime: 'automatic',
+ },
+ ],
'@babel/preset-typescript',
],
plugins: ['@babel/plugin-transform-runtime'],

修改 eslintrc 的設定

+      peerDependencies: true,
packageDir,
},

Testing for React Component

setup for testing react components @ pjchender/mono-react-ts-template

  • packages/components 中安裝對應的套件(這裡我們假設 packages/core 不會用到 react,如果會的話,或許可以把 @testing-library 裝在根目錄):

    npm install -D @testing-library/react \
    @testing-library/jest-dom \
    @testing-library/react-hooks \
    @testing-library/user-event \
    @types/styled-components \
    react-test-renderer
  • 在根目錄建立 jest.config.js,設定 testEnvironmentjsdom,並且使用 '@testing-library/jest-dom/extend-expect'

    // jest.config.js
    process.env.TZ = 'UTC';

    module.exports = {
    testEnvironment: 'jsdom',
    setupFilesAfterEnv: ['@testing-library/jest-dom/extend-expect'],
    };
  • packages/* 中建立 jest.config.js 並把根目錄的設定 import 進來

Setup Styled-Components

setup styled-components for babel and jest @ pjchender/mono-react-ts-template

babel plugin

babel-plugin-styled-components @ styled-components

使用 babel-plugin-styled-components

  • 透過 ssr 的設定,避免 client 端和 server 端產生的 className 名稱不同
  • 透過 displayName 的設定,把元件的名稱顯示在 className 中方便 debug
  • Minification:可以透過 minify 的設定將空白和註解移除;透過 transpileTemplateLiterals 只留下有用的值
  • Dead Code Elimination:透過 pure 的設定,會在每一個 styled-components 和 helper 前加上 #__PURE__ 的 comment,以變其它 minifier 可以解決這個問題
npm install --save-dev babel-plugin-styled-components

Jest Styled Components

Jest Styled Components @ Github

  • 使用 jest-styled-components,它可以把 Styled Components 的樣式產生 snapshot 中,讓開發者看到是什麼樣式改變了,而不會只是看到一堆 className。
  • 可以透過在 jest.config.js 中透過 setupFilesAfterEnv: ['jest-styled-components'] 的設定來套用這個套件。
  • 如果是 monorepo 的話,一定要安裝在 packages/* 內,不能只是裝在根目錄,否則會沒有效
注意