<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          2021 年 TypeScript + React 前端工程化開發(fā)指北

          共 28072字,需瀏覽 57分鐘

           ·

          2021-09-21 13:35

          點擊上方 前端瓶子君,關(guān)注公眾號

          回復(fù)算法,加入前端編程面試算法每日一題群

          1. 前言

          大約在四年前,我先后寫了《再見,babel-preset-2015》以及《為什么我們要做三份 Webpack 配置文件》,在知乎上受到不錯的反響,雖然已經(jīng)過去很多年,但是讓我意外的是至今仍有很多讀者在閱讀和點贊。

          前端技術(shù)棧在這些年間不斷演進,已經(jīng)發(fā)生了很大的變化,但是從零開始打造一個項目仍然是較為困難的事情,網(wǎng)上的文章實在太多太零散,有些幾個月前的文章現(xiàn)在看來都會都會過時。我們團隊大約在 2018 年切換到了 TypeScript 技術(shù)棧,包括 TypeScript + React + AntDesign + GraphQL。

          因此,在 2021 年夏末,我想重新寫一篇關(guān)于前端工程化入門配置的指南。

          本文中即將涉及的技術(shù)棧包括(按照字母表順序):

          • ESLint:校驗?zāi)愕?TypeScript / JavaScript 代碼。
          • CommitLint:校驗?zāi)愕?Git 提交消息。
          • Git:主流代碼倉庫。
          • Husky:快速添加 Git 鉤子的工具。
          • Less:更友好的樣式語言。
          • LintStaged:校驗在 Git staged 階段的代碼。
          • Prettier:可以對各種源文件進行格式化的工具。
          • React:我們目前使用的前端框架,版本是 17。盡管如此,Angular 及 Vue 技術(shù)棧的同學(xué)依然可以毫無障礙的閱讀本文。
          • TypeScript:不僅包含 JavaScript 的語法,而且還提供了靜態(tài)類型檢查以及面向?qū)ο缶幊烫匦浴?
          • Vite:新一代構(gòu)建及打包工具,由于在 dev 模式下采用了 esbuild 作為底層 bundler,因此大幅提高了構(gòu)建速度,特別是在開發(fā)態(tài)。Vite 的周邊生態(tài)逐步完善中。
          • Webpack:目前最流行的前端構(gòu)建及打包工具,本文中的版本是最新的 5.x
          • Yarn:一個或許比 NPM 更友好、更快捷的包依賴管理工具,我們團隊內(nèi)部已經(jīng)完全切換至 Yarn。

          Vite 的出現(xiàn)為前端工程化帶來了顯而易見的便利性和開發(fā)效率,本文會有兩個分支,分別是 Webpack 和 Vite。對應(yīng)的 Github 也會有 masterwebpack 分支,master 使用的是 Vite 構(gòu)建器。


          2021 年夏天寫的文章雖然可能會過時,但是 GitHub 上的項目會持續(xù)更新,歡迎大家 Star 上面的 GitHub 項目。

          點擊圖中的 Use this template 按鈕

          我已經(jīng)將該倉庫設(shè)置為 Github Template,因此,直接點擊 Use this template 按鈕,即可從該模板創(chuàng)建你自己的項目。

          你也可以點擊這里用最新的模板在 Github 上創(chuàng)建新項目。


          2. 從零開始手工搭建一個新項目

          2.1 創(chuàng)建項目目錄

          首先讓我們創(chuàng)建一個新目錄作為項目的根目錄,目錄的命名方式我們建議由英文小寫字母及數(shù)字組成,單詞或數(shù)字之間用 - 分隔,如 my-boilerplateboilerplate-2021,不推薦的命名包括 myboilerplateMyBolierplatewo-de-mu-ban 等。

          mkdir boilerplate-2021
          cd boilerplate-2021

          2.2 初始化 Git 倉庫

          養(yǎng)成好習(xí)慣,在每次開始一個新項目的時候,首先要創(chuàng)建 Git 本地倉庫,請在項目根路徑中執(zhí)行:

          git init

          當(dāng)然,第 1 步和第 2 步也可以先在 Github 或 Gitlab 上創(chuàng)建好項目,然后通過 git clone 命令復(fù)制下來。這樣帶來的好處是在接下來的 package.json 中會自動填寫上作者、Git Repository 地址等。

          接下來在項目根路徑中創(chuàng)建一個 .gitignore 文件,聲明我們不要上傳至 Git 的文件:

          # MacOS / Windows 等系統(tǒng)文件
          .DS_Store
          Thumbs.db

          # Node 依賴目錄
          node_modules/

          # 輸出目錄
          /dist
          *.tsbuildinfo

          # 日志
          *.log

          # 運行時數(shù)據(jù)
          pids
          *.pid
          *.pid.lock

          # 集成開發(fā)環(huán)境配置文件
          /.vscode

          # ESLint 緩存
          .eslintcache

          請注意,以 / 開頭的路徑表示強調(diào)必須是根路徑下的文件夾;而以 / 結(jié)尾的路徑強調(diào)是目錄。

          這里有一份更全的 .gitignore 文件,可以作為參考。

          2.3 創(chuàng)建 README.md

          通常我們創(chuàng)建的第一個文檔就是 README.md 文件:

          # 項目名稱

          在這里添加項目的概述。

          ---

          ## 如何安裝

          如果這是一個提供給第三方使用的 NPM 包,你需要在這里提供安裝命令。

          通過 NPM 安裝:

          ```sh
          npm install boilerplate-2021

          或通過 Yarn 安裝:

          yarn add boilerplate-2021

          如何在本地開發(fā)

          在這里告訴開發(fā)人員如何在本地安裝和開發(fā)。


          `README.md` 是可以體現(xiàn)你的開源合作精神的地方,你可以盡可能的讓未來使用你的 NPM 包或接手你工作的同學(xué)省心省力。這里有一份[開源標(biāo)準](https://link.zhihu.com/?target=https%3A//github.com/standard/standard/blob/master/README.md)可以參考。

          `CHANGELOG.md` 也是重要的文件之一,你可以參考這份[開源標(biāo)準](https://link.zhihu.com/?target=https%3A//github.com/standard/standard/blob/master/CHANGELOG.md)。

          > 更多文檔標(biāo)準可以參考 [https://github.com/standard/standard/](https://link.zhihu.com/?target=https%3A//github.com/standard/standard/)。

          ### 2.4 生成 `package.json`

          本文中所有 NPM 包管理工作都交給 [Yarn](https://link.zhihu.com/?target=https%3A//yarnpkg.com/),如果你對 Yarn 還不熟悉,請參考[這篇文檔](https://link.zhihu.com/?target=https%3A//yarn.bootcss.com/docs/install/%23mac-stable),同時建議參考[這篇文檔](https://link.zhihu.com/?target=https%3A//learnku.com/articles/15976/yarn-accelerate-and-modify-mirror-source-in-china)將源設(shè)為國內(nèi)的鏡像。

          > 當(dāng)然你也可以使用 [PNPM](https://link.zhihu.com/?target=https%3A//pnpm.io/zh/) 取代 `yarn`,本文中將以 `yarn` 為默認的包管理工具。

          接下來,讓我們創(chuàng)建 `package.json` 文件,在項目根路徑中執(zhí)行:

          ```bash
          yarn init

          連續(xù)若干個 Enter 鍵后,你將看到這樣一份簡潔代碼:

          {
            "name""boilerplate-2021",
            "version""1.0.0",
            "main""index.js",
            "author""Henry Li <[email protected]>",
            "license""MIT"
          }

          建議始終通過 yarn initnpm init 創(chuàng)建 package.json 文件,并只通過 yarnnpm 命令修改項目的依賴。

          2.5 引入 TypeScript 技術(shù)棧

          我們首先要引入的是 TypeScript 技術(shù)棧,并且創(chuàng)建 tsconfig.json,最簡單的方式就是交給 tsc 命令:

          yarn add -D typescript && npx tsc --init

          上方的命令中我們先借助 [yarn add](https://link.zhihu.com/?target=https%3A//yarn.bootcss.com/docs/cli/add/) 命令添加了 TypeScript 的主包,并且通過 Node 提供的 [npx](https://link.zhihu.com/?target=https%3A//www.ruanyifeng.com/blog/2019/02/npx.html) 命令直接執(zhí)行 TypeScript 命令行工具創(chuàng)建默認的 tsconfig.json

          建議始終先通過 tsc --init 命令創(chuàng)建 tsconfig.json 文件,并且保留其中的注釋。

          基于官方的配置文件,建議設(shè)置以下選項:

          • incremental 設(shè)置為 true,允許增量編譯,有助于加快編譯速度。
          • target 設(shè)置為 ESNEXT,即直接輸出為最新的 ES 標(biāo)準。
          • module 設(shè)置為 ESNext,即面向未來的 ESM 模塊化。
          • allowJScheckJS 設(shè)置為 true,允許編譯 JavaScript 文件。
          • jsx 設(shè)置為 react-jsx,本文中我們不會使用到 Babel,因此是直接通過 TSCJSX 代碼片段編譯為 JS 代碼片段。另外,react-jsx 是 TypeScript 在 4.1 引入的新特性,它可以讓我們不需要再每一個 JSX / TSX 文件中寫 import React from 'react'語句。
          • outDir 設(shè)置為 ./dist/esdist 是我們的發(fā)行(distribution)根目錄,而 es 是我們默認的 ESM 模塊發(fā)行目錄。
          • rootDir 設(shè)置為 ./src,這是我們存放源代碼的目錄,請順手創(chuàng)建。
          • strict 改為 true,即啟用所有嚴格類型檢查選項。
          • moduleResolution 改為 node,將模塊解析模式設(shè)為 Node.js。
          • allowSyntheticDefaultImports 改為 true,這樣可以讓 import React from 'react' 這樣的語句不會報錯。當(dāng)然如今 esModuleInterop 已經(jīng)默認開啟,也會起到隱式聲明的作用。
          • 如果你和我一樣需要用到 decorator 特性,需要將experimentalDecoratorsemitDecoratorMetadata 改為 true
          • 如果開發(fā)的是一個 NPM 包項目,declaration 需要改為 true
          • 最后加上 includeexclude 選項,告訴編譯器需要編譯和忽略什么。

          這里有一份簡明 tsconfig.json 作為參考,但是強烈建議按照上面的步驟手工進行配置,并且保留所有的注釋及未使用的選項:

          {
            "compilerOptions": {
              "target""ESNEXT",
              "module""ESNext",
              "allowJs"true,
              "checkJS"true,
              "jsx""react-jsx",
              "declaration"true,
              "sourceMap"true,
              "outDir""./dist/es",
              "rootDir""./src",
              "strict"true,
              "moduleResolution""node",
              "allowSyntheticDefaultImports"true,
              "esModuleInterop"true,
              "experimentalDecorators"true,
              "emitDecoratorMetadata"true,
              "skipLibCheck"true,
              "forceConsistentCasingInFileNames"true
            },
            "include": ["src/**/*"],
            "exclude": ["node_modules"]
          }

          接下來,讓我們驗證一下 TypeScript 的設(shè)置,創(chuàng)建并編寫 src/index.tsx

          function hello(name: string{
            console.info(`Hello ${name}`);
          }

          hello('world');

          通過 tsc 命令編譯并執(zhí)行:

          npx tsc && node dist/es/index.js

          此時,你還需檢查在 /dist/es 目錄中是否包含 TypeScript 聲明文件( *.d.ts) 和源代碼映射文件( *.js.map )。如果看見 /dist/tsconfig.tsbuildinfo 文件則說明 incremental 選項生效了。

          這里有一篇文章,包含更多關(guān)于 tsconfig.json 的知識,可以作為參考。

          2.6 引入 Prettier

          在我們寫代碼前,強烈建議引入 [prettier](https://link.zhihu.com/?target=https%3A//prettier.io/),它會讓我們的代碼風(fēng)格更加統(tǒng)一和規(guī)范,支持包括 JavaScript、TypeScript、HTML、CSS/LESS 甚至 Markdown 在內(nèi)的多種文件的格式化。如果你是 VSCode 的用戶,你需要提前安裝 Prettier - Code formatter 插件。

          yarn add -D prettier

          執(zhí)行上面的命令,并將下面的代碼保存為 .prettierrc 文件:

          {
            "semi"true,
            "singleQuote"true,
            "trailingComma""es5"
          }

          在 VSCode 中,你可以通過 Format Document 命令執(zhí)行格式化,也可以通過在 Workspace 配置文件中選中 Editor: Format On Save 選項實現(xiàn)保存前自動格式化。

          曾經(jīng) prettiereslint 之間就格式化問題有過沖突,但你早已不必擔(dān)心此事,后文中有解法。

          2.7 引入 ESLint

          ESLint 如今已經(jīng)成為前端代碼校驗的必備工具,TypeScript 過去需要依賴 tslint 工具進行校驗,而現(xiàn)在早已被 ESLint 及一系列的 plugin 取代。安裝 ESLint 的命令如下:

          yarn add -D eslint
          yarn add -D @typescript-eslint/eslint-plugin @typescript-eslint/parser
          yarn add -D eslint-config-airbnb eslint-config-airbnb-typescript
          yarn add -D eslint-plugin-import eslint-plugin-jsx-a11y
          yarn add -D eslint-config-prettier eslint-plugin-react eslint-plugin-react-hooks

          這里我們選用的是 eslint-config-airbnb 配置,它對 JSX、Hooks、TypeScript 及 A11y 無障礙化都有良好的支持,可能也是目前最流行、最嚴格的 ESLint 校驗之一。

          接下來,創(chuàng)建 ESLint 配置文件 .eslintrc.js:

          // .eslintrc.js

          module.exports = {
            roottrue,
            parser'@typescript-eslint/parser',
            extends: [
              'airbnb',
              'airbnb-typescript',
              'airbnb/hooks',
              'plugin:@typescript-eslint/recommended',
              'plugin:@typescript-eslint/recommended-requiring-type-checking',
              'plugin:react/jsx-runtime',
              'prettier',
            ],
            parserOptions: {
              project'./tsconfig.json',
            },
            rules: {
              // 在這里添加需要覆蓋的規(guī)則
            }
          };

          我們之所以選擇 .js 的后綴,是為了將來有更好的擴展性,你可以在這里根據(jù)上下文及條件“拼裝”復(fù)雜的規(guī)則。

          由于我們在 tsconfig.json 中開啟了 react-jsx 選項。因此我們需要添加 plugin:react/jsx-runtime 這個配置集,這樣當(dāng)我們不去寫 import React from 'react' 時,ESLint 不會報錯。

          此外,永遠將 prettier 放置在 extends 的末尾,這是為了關(guān)閉其與 ESLint 有沖突的規(guī)則。

          2.8 引入 React 技術(shù)棧

          前文介紹的技術(shù)棧基本沒有涉及到前端框架,從本節(jié)開始我們將介紹如何快速在 TypeScript 環(huán)境中引入 React 17 技術(shù)棧,首先通過下面的 2 行命令安裝 React 及其 TypeScript 描述。

          yarn add react react-dom    
          yarn add -D @types/react @types/react-dom

          將 src/index.tsx 的內(nèi)容修改為:

          import { render } from 'react-dom';

          import { Hello } from './components/Hello';

          function App() {
          return <Hello name="world" />;
          }

          render(<App />, document.getElementById('react-mount-point'));

          添加 src/components 目錄,并且創(chuàng)建 src/components/Hello/index.tsx。我們團隊遵循每一個 React 組件代碼放置在獨立目錄的原則。

          export interface HelloProps {
          className?: string;
          name: string;
          };

          export function Hello({ className, name }: HelloProps) {
          return <div className={className}>Hello {name}</div>;
          }

          本文遵循的是 create-react-app 中的代碼風(fēng)格和標(biāo)準。你也可以用 type 來聲明 HelloProps,通常兩者是等價的,除了一些特殊情況。因為我們在 tsconfig.json 中將 compilerOptions.jsx 設(shè)置為 react-jsx,因此我們不需要在 TSX 文件中顯示引入 react。


          3. Webpack 分支

          作為老牌勁旅,Webpack 依然是 2021 年最流行的打包工具。相對于 Vite 來說它的功能更加強大,但是性能較低且配置復(fù)雜度偏高,如果你不想使用 Webpack,可以跳過本章節(jié),直接到 Vite 分支 即本文的第 4 節(jié)。

          3.1 引入 Webpack 構(gòu)建器

          本文將以 Webpack 5 為主,首先快速安裝 webpack*ts-loader

          yarn add -D webpack webpack-merge webpack-cli webpack-dev-server webpackbar clean-terminal-webpack-plugin ts-loader fork-ts-checker-webpack-plugin

          Webpack 的配置通常會有三份,至于原因可以閱讀我多年前的拙文《為什么我們要做三份 Webpack 配置文件》:

          • common 通用配置:包含 Webpack 基礎(chǔ)通用 配置信息,環(huán)境中立。
          • dev 開發(fā)配置:繼承自 base,包含開發(fā)態(tài)的特殊配置,為開發(fā)環(huán)境。
          • prod 生產(chǎn)配置:同樣繼承自 base,包含線上最終態(tài)的特殊配置,為生產(chǎn)環(huán)境。

          所有工程化腳本相關(guān)的 JS 代碼建議都放在項目根路徑下的 scripts 目錄,首先讓我們創(chuàng)建 scripts/webpack/webpack.common.config.js

          // scripts/webpack/webpack.commo.config.js

          const path = require('path');

          const CleanTerminalPlugin = require('clean-terminal-webpack-plugin');
          const ProgressBarPlugin = require('webpackbar');

          module.exports = {
            entry: { index: path.resolve(__dirname, '../../src/index.tsx') },
            output: {
              filename'[name].js',
              path: path.resolve(__dirname, '../../dist'),
              cleantrue,
            },
            stats"errors-warnings",
            resolve: {
              extensions: ['.ts''.tsx''.js''.jsx'],
            },
            module: {
              rules: [
                {
                  test/\.tsx?$/,
                  exclude/node_modules/,
                  loader'ts-loader',
                },
              ],
            },
            plugins: [new CleanTerminalPlugin(), new ProgressBarPlugin()],
            optimization: {
              usedExportsfalse,
            },
          };

          上面的代碼是一份包含 ts-loader 的極簡配置,在本例中無需使用到 Babel 技術(shù)棧。

          你可以訪問 [Webpack 的官方文檔](Optimization | webpack 中文文檔) 了解和添加更多優(yōu)化選項。

          有了通用配置之后,我們需要定義開發(fā)和生產(chǎn)環(huán)境下的 Webpack 配置,這里我們通過 [webpack-merge](https://link.zhihu.com/?target=https%3A//www.webpackjs.com/guides/production/) 實現(xiàn)繼承 common 配置。

          // scripts/webpack/webpack.dev.config.js

          const { merge } = require('webpack-merge');

          const common = require('./webpack.common.config');

          module.exports = merge(common, {
            mode'development',
            devtool'cheap-module-source-map',
            devServer: {
              // 添加 webpack-dev-server 開發(fā)服務(wù)器的配置
            }
          });
          // scripts/webpack/webpack-prod-config.js

          const { merge } = require('webpack-merge');

          const common = require('./webpack.common.config');

          module.exports = merge(common, {
            mode'production',
          });

          為了更好的測試我們的編譯結(jié)果,你還需要添加 public/index.html

          <!DOCTYPE html>
          <html lang="en">
          <head>
            <meta charset="UTF-8">
            <meta http-equiv="X-UA-Compatible" content="IE=edge">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>boilerplate-2021</title>
          </head>
          <body>
            <div id="react-mount-point"></div>
            <script src="index.js"></script>
          </body>
          </html>

          publicwebpack-dev-server 默認的靜態(tài)資源目錄。

          最后,修改 package.json ,添加 scripts 腳本執(zhí)行:

          {
            ...
            "scripts": {
              "build""webpack --config scripts/webpack/webpack.prod.config.js",
              "dev""webpack serve --config scripts/webpack/webpack.dev.config.js",
              "start""npm run dev"
            },
          }

          現(xiàn)在,你可以執(zhí)行 yarn buildyarn start 檢查以上 Webpack 配置是否正確。

          3.2 引入 Less 技術(shù)棧

          直到目前為止,我們還沒有涉及到任何樣式相關(guān)的技術(shù)棧,在本章節(jié)中我們將引入 Less 技術(shù)棧,同樣是從命令行安裝開始:

          yarn add -D less less-loader style-loader css-loader
          yarn add classnames

          [classnames](https://link.zhihu.com/?target=https%3A//github.com/JedWatson/classnames) 庫幾乎是每一個項目都會用到的經(jīng)典庫。

          修改 scripts/webpack/webpack.common.config.js 文件,添加如下配置(在 module.rule 配置章節(jié)中追加):

          module.exports = {
            ...
            module: {
              rules: [
                ...
                {
                  test/\.less$/,
                  exclude/node_modules/,
                  use: [
                    'style-loader',
                    'css-loader',
                    'less-loader',
                  ],
                },
              ],
            },
          };

          接下來,我們需要進行測試,添加 src/components/Hello/index.module.less 文件:

          .container {
          color: red;
          }

          *.module.less 的文件將被編譯為 *.module.css 并輸入到 css-loadercss-loader 會默認將為 .module.css 結(jié)尾的文件自動開啟 CSS 模塊化。因此,我們在該文件中聲明的選擇器都是本地的,在編譯時會加上前綴及哈希值。有關(guān) CSS 模塊化的內(nèi)容,請參考這篇文章。

          然后在 src/components/Hello/index.tsx 文件中引用 less 文件:

          import cn from 'classnames';
          import React from 'react';

          import styles from './index.module.less';

          export interface HelloProps {
          className?: string;
          name: string;
          }

          export function Hello({ className, name }: HelloProps) {
          return <div className={cn(styles.container, className)}>Hello {name}</div>;
          }

          這時,我們會發(fā)現(xiàn)編譯器報錯:

          TS2307: Cannot find module './index.module.less' or its corresponding type declarations.

          這是因為我們沒有為 *.module.less 文件聲明其導(dǎo)出模塊的類型,熟知 CSS 模塊化的小伙伴們一定知道 less 模塊導(dǎo)出的是一個映射表 JSON,鍵是我們在 less 文件中為選擇器取得本地局部名稱,而值則是 CSS 模塊化編譯后的運行時名稱(被加前后綴、混淆及哈希化后的值)。

          但是 TypeScript 并不知道這一點,因此我們需要在 src/types 目錄中添加 less.d.ts 模塊聲明文件:

          declare module '*.module.less' {
            const map: Record<stringstring>;
            export = map;
          }

          搞定,現(xiàn)在我們終于可以正常引用 *.module.less 文件了。

          3.3 集成 ESLint

          接下來,讓我們添加 Webpack 插件使其與 ESLint 集成,在過去我們通常需要使用eslint-loader,然而 2021 年的當(dāng)下它已經(jīng)被歸檔,取而代之的是 fork-ts-checker-webpack-plugin (我們剛才已經(jīng)安裝過),它可以獨立的進程中進行 TypeScript 的類型檢查,同時也可以集成 ESLint,為此我們需要修改 scripts/webpack/webpack.common.config.js 文件,首先通過 `transpileOnly` 禁用 ts-loader 的類型檢查,添加如下配置:

          const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');

          ...

          module.exports = {
            module: {
              rules: [
                {
                  test/\.tsx?$/,
                  exclude/node_modules/,
                  loader'ts-loader',
                  options: {
                    transpileOnlytrue,
                  },
                },
                ...
            ]
            ...
            plugins: [
              ...
              new ForkTsCheckerWebpackPlugin({
                eslint: {
                  files'./src/**/*.{ts,tsx,js,jsx}',
                },
              }),
            ],
          };

          最后,再次強調(diào) ESLint 的重要性,盡量不要隨意關(guān)閉 react-app 中定義的規(guī)則,至少在關(guān)閉規(guī)則前需要仔細閱讀該規(guī)則的說明和錯誤示例。另外,強烈建議你安裝 ESLint 的 VSCode 插件,這樣你可以借助 Quick Fix 功能提供的修改意見快速修正代碼。

          3.4 支持模塊熱更新

          模塊熱更新(Hot Module Reload,簡稱為 HMR)是深受開發(fā)者喜愛的工具,然而 React 17 + Webpack 5 + TypeScript 技術(shù)棧下的 HMR 卻撲朔迷離,我查了很多文檔都沒有 100% 適合當(dāng)下的。感謝讀者 baxtergu 的提示,我們采用的是 create-react-app 中也用到的 [react-refresh-webpack-plugin + react-refresh](https://link.zhihu.com/?target=https%3A//github.com/%253C/code%253Epmm%253Ccode%253Emwh/react-ref%253C/code%253Eresh-webpack-plugin):

          yarn add -D @pmmmwh/react-refresh-webpack-plugin react-refresh react-refresh-typescript

          在筆者寫這篇文章的時候,必須使用 react-refresh-webpack-plugin 的 0.5.0-rc5 才能奏效,默認安裝的 0.4.3 版本未能與 Webpack 5.51.1 匹配,這個版本是 8 天前剛更新的。

          接下來,將你的 scripts/webpack/webpack.dev.config.js 中的內(nèi)容替換為:

          const webpack = require('webpack');
          const { merge } = require('webpack-merge');

          const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
          const ReactRefreshTypeScript = require('react-refresh-typescript');

          const common = require('./webpack.common.config');

          const tsLoader = common.module.rules.find((r) => r.loader === 'ts-loader');
          if (tsLoader) {
            tsLoader.options = {
              ...tsLoader.options, // 可能為 undefined
              getCustomTransformers() => ({
                before: [ReactRefreshTypeScript()],
              }),
              transpileOnlytrue,
            };
          }

          module.exports = merge(common, {
            mode'development',
            devtool'cheap-module-source-map',
            devServer: {
              client: {
                overlay: {
                  warningsfalse,
                },
              },
            },
            plugins: [
              new webpack.HotModuleReplacementPlugin(),
              new ReactRefreshWebpackPlugin(),
            ],
          });

          這里我們添加了 Webpack 自帶的 HotModuleReplacementPlugin 和 剛才我們安裝的 ReactRefreshWebpackPlugin,并且在 ts-loader 中添加了自定義的 transformer。與傳統(tǒng)的 [react-hot-reloader](https://link.zhihu.com/?target=https%3A//github.com/gaearon/react-hot-loader) 相比,使用 react-refresh 的好處是你只需要修改 dev 環(huán)境下的 Webpack 配置,并且完全無需對你自己的源代碼進行修改。


          4. Vite 分支

          Vite 目前還在不斷改進和升級中,以下配置僅保證當(dāng)前可用,請訪問我的 Github 項目以保持更新。GitHub - MagicCube/boilerplate-2021: Yet another boilerplate for TypeScript and React developers.

          4.1 引入 Vite 構(gòu)建器

          Vite 已經(jīng)集成了包括 TypeScript、 CSS 處理器在內(nèi)的構(gòu)建配置,因此大大降低了配置難度,并且顯著減少了 NPM 包的數(shù)量,

          接下來讓我們來安裝一下 Vite:

          yarn add -D vite less vite-plugin-linter vite-react-jsx @vitejs/plugin-react-refresh

          盡管 Vite 已經(jīng)集成了 Less、SCSS 等主流的 CSS 處理器,但是你仍然需要手動安裝 less。當(dāng)然,在 Vite 的官方文檔中,鼓勵直接使用 CSS 提供的變量及 PostCSS 的嵌套語法,從而徹底拋棄 Less 等預(yù)處理器。

          下一步很重要,你需要在 tsconfig.json 中將 isolatedModules 設(shè)置為 true

          {
             ...
             "isolatedModules"true
             ...
          }

          為了讓我們能夠在代碼中可以用上類似 import.meta.env.MODE 這樣的環(huán)境變量,我們還需要在 src/typings 中添加 vite.d.ts

          /// <reference types="vite/client" />

          然后,讓我們在項目根路徑下創(chuàng)建 vite.config.js

          import { defineConfig } from 'vite';

          import reactRefresh from '@vitejs/plugin-react-refresh';
          import { EsLinter, linterPlugin } from 'vite-plugin-linter';
          import reactJsx from 'vite-react-jsx';

          export default defineConfig((configEnv) => {
            return {
              base'',
              resolve: {
                alias: [{ find'@'replacement'/src' }],
              },
              plugins: [
                linterPlugin({
                  include: [
                    './src/**/*.ts',
                    './src/**/*.tsx',
                    './src/**/*.js',
                    './src/**/*.jsx',
                  ],
                  linters: [new EsLinter({ configEnv })],
                }),
                reactJsx(),
                reactRefresh(),
              ],
            };
          });

          可以看到我們的配置十分簡單,一共只引入了三個額外的 Vite 插件,它們分別是:

          • vite-plugin-linter:Vite 的插件體系還正在不斷成長中,我找了 3 個 ESLint 相關(guān)的插件,最后只有這一個完全符合我們的要求,它可以和 Webpack 的 Fork TS Check 插件一樣啟動一個子進程執(zhí)行 ESLint 等類型校驗,并且還支持其他的校驗工具。
          • plugin-react-refresh:這個插件可以實現(xiàn) React 熱模塊更新的功能。
          • vite-react-jsx:因為我們在 tsconfig.json 中開啟了 react-jsx 開關(guān),即使用了 React 17 之后的免去 import React from 'react' 聲明的特性,因此我們需要引入這個插件。

          這里你會發(fā)現(xiàn)我們使用了 defineConfig((configEnv: ConfigEnv) => UserConfig) 重載來設(shè)置配置項,這是為了將來可以根據(jù)環(huán)境變量等不確定條件決定配置項的值。

          4.2 創(chuàng)建 HTML

          Vite 構(gòu)建的入口文件通常是一個 HTML 文件,我們可以在這里引入 TypeScript、JavaScript 的源文件,只需在 <script/> 標(biāo)簽中添加上 type="module" 的聲明即可。在 production 構(gòu)建時,Vite 會幫你處理好一切,甚至?xí)湍阕詣哟虬?vendor.jsindex.js,分別包含第三方包和你自己的程序。

          <!DOCTYPE html>
          <html lang="en">
            <head>
              <meta charset="UTF-8" />
              <meta http-equiv="X-UA-Compatible" content="IE=edge" />
              <title>boilerplate-2021</title>
              <meta name="viewport" content="width=device-width, initial-scale=1.0" />
              <link rel="icon" type="image/svg+xml" href="./resources/favicon.svg" />
            </head>
            <body>
              <div id="react-mount-point"></div>
              <script type="module" src="./src/index.tsx"></script>
            </body>
          </html>

          除了程序的源代碼,你還可以引入 CSS 等樣式文件,甚至可以引入 favicon 等圖片資源文件。需要注意的是,Vite 默認的公共文件目錄是 public,存放在 public 目錄下的資源文件名將不會被添加哈希,而放置在其他目錄下的文件則會自動添加上哈希,比如在上面的代碼中放置在 resources 目錄下的 favicon.svg 文件就會被添加上哈希。

          4.3 配置 package.json

          在你的 package.json 中,添加以下構(gòu)建(build)、開發(fā)(dev)及預(yù)覽(preview)命令:

          {
            ...
            "scripts": {
              "build""npm run build:vite",
              "build:es""tsc",
              "build:vite""vite build",
              "dev""vite",
              "start""npm run dev",
              "preview""vite preview"
            }
          }

          這里的預(yù)覽是指查看 production 生產(chǎn)環(huán)境下的結(jié)果。

          是的,現(xiàn)在你已經(jīng)完成了所有 Vite 所需的配置。更多配置請查看 Vite 的官方文檔。


          5. 管控你的 Git 提交

          在很多企業(yè)和開源項目中,都有一個關(guān)于 Git 的原則,即 ”提交 commit 前,必須保證沒有編譯時異常“。對于前端來說就是必須讓每一個 commit 都符合 TypeScript 編譯器及 ESLint 的考驗。

          此外,你可能經(jīng)常會在 Github 上看到開源項目的 commit 消息都是以 featfixchore 等開頭,這其實是 commit-lint 規(guī)范:

          • feat:新功能(feature)變更,大多數(shù) commit 都屬于這種類型。
          • fix:修復(fù)缺陷(bug)變更。
          • docs:修改或添加文檔(documentations)及代碼注釋變更。
          • style:僅由代碼格式化造成的變更。
          • refactor:重構(gòu)(refactor)變更。
          • test:由測試相關(guān)的變更。
          • chore:構(gòu)建過程、配置及輔助工具相關(guān)的變更,十分常用。

          為了實現(xiàn)對每一條 Git 變更提交進行如上校驗,我們需要引入 huskylint-stagedcommit-lint,最佳安裝方式是通過執(zhí)行命令:

          yarn add -D husky lint-staged @commitlint/config-conventional @commitlint/cli

          接下來,讓我們配置 lint-staged,使其對 Git staged 狀態(tài)中的源代碼執(zhí)行 eslint,為此我們要添加 .lintstagedrc.json

          {
            "*.{js,jsx,ts,tsx}": ["eslint --cache"]
          }

          我們還需要配置 commit-lint,它是負責(zé)校驗 Git commit 消息的工具,之后的 message 必須以上述單詞縮寫和冒號開頭,請大家添加 commitlint.config.js

          module.exports = { extends: ['@commitlint/config-conventional'] };

          安裝 husky,這將把 husky 掛到 Git 提供的 hooks 上。接著我們還需確保將安裝 husky 的腳本掛在 NPM 的 prepare hooks 上:

          npm set-script prepare "husky install"
          npm run prepare

          最后,下面的命令行讓我們將 lint-stagedcommit-lint 掛在 Husky 的 hooks 上:

          npx husky add .husky/pre-commit "npx --no-install lint-staged"
          npx husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"'

          這里有一個坑,上面的這段命令是官方提供的,但事實上在 MacOS 中會出現(xiàn)問題,因此你還需要手工將 .husky/commit-msg 文件修改為:

          #!/bin/sh
          "$(dirname "$0")/_/husky.sh"

          npx --no-install commitlint --edit "$1"

          好了,現(xiàn)在如果你的代碼中有編譯異常、ESLint 錯誤或是提交的 message 不標(biāo)準,都會拋出異常,即便在 VSCode 里提交也一樣。


          6. 更新 package.json

          最后,我們還需要為我們的 NPM 包添加 ES 模塊化和 TypeScript 類型聲明,請在 package.json 中添加:

          {
            ...
            "module""/dist/es/index.js",
            "types""./dist/es/index.d.ts",
          }

          如果開發(fā)的是一個 NPM 包項目,你可以在此基礎(chǔ)上添加 descriptionrepositorykeywordshomepage 等信息,并且需要額外添加 common-js 風(fēng)格的編譯目標(biāo),并在 package.jsonmain 字段中聲明。


          7. 進階功能

          在該模板中,還內(nèi)置了多主題樣式等功能,在這里就不一一贅述。此外,我還集成 GitHub 模板、GitHub Pages 站點等功能,前者能夠讓其他開發(fā)者直接使用我們的腳手架創(chuàng)建新的 GitHub 倉庫,而后者則可以將我們構(gòu)建出來的站點在 github.io 上發(fā)布,就像這樣。同時,我還集成了 Travis CI 工具,這樣只要有 master 分支上的更新,就會觸發(fā)持續(xù)集成構(gòu)建,構(gòu)建成功后就會自動幫我們更新 GitHub Pages 站點。


          8. 結(jié)語

          本文到這里就即將結(jié)束,不得不說每隔一段時間去重溫一次從零搭建腳手架,都受益匪淺。當(dāng)然在阿里巴巴 CCO 技術(shù)部,我們早已無需手工搭建腳手架,在內(nèi)部我們自研了具有自身特色的模板工具,可以秒建一個新項目,如果你也對前端工程化有興趣,或是對本文中的步驟、代碼有疑義,歡迎在下方留言。

          關(guān)于本文

          來源:Henry

          https://zhuanlan.zhihu.com/p/403970666


          最后

          歡迎關(guān)注【前端瓶子君】??ヽ(°▽°)ノ?
          回復(fù)「算法」,加入前端編程源碼算法群,每日一道面試題(工作日),第二天瓶子君都會很認真的解答喲!
          回復(fù)「交流」,吹吹水、聊聊技術(shù)、吐吐槽!
          回復(fù)「閱讀」,每日刷刷高質(zhì)量好文!
          如果這篇文章對你有幫助,在看」是最大的支持
           》》面試官也在看的算法資料《《
          “在看和轉(zhuǎn)發(fā)”就是最大的支持


          瀏覽 37
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  成人一级aa黄色电影 | 蜜臀av一区 | 色色爱爱 | 波多野结衣性爱视频 | 欧美日本一频道 |