vite+antd+ts構建標准化react應用


背景

之前公司項目采用的是umi腳手架一體化構建工具,得益於對webpack與各框架的集成和封裝,使得快速上手的能力大大加強,但是隨着項目的不斷迭代與功能增加,依賴的庫也是越來越多,目前最明顯的感受就是每次啟動與打包構建的時長,往往是好幾分鍾~,熱更新有時也要耗費數秒,對於開發效率與體驗影響很大。。。

之前尤大發布vite1.0時也了解了一點,最明顯感受就是一個字“快”,不過一直沒仔細研究過,只知道是基於`esbuild`和`rollup`,目前`vite2.0`已經發布,完全作為一個獨立的構建工具,對`react`等其他非`vue`框架有着很好的支持。最近也算忙里偷閑,算是稍微研究了一下基本知識。本篇文章記錄我以`vite`構建`react`的過程及細節,后續會繼續深入研究輸出`vite`相關系列文章,敬請期待

目標

我對構建項目的要求如下:

  • 支持Typescript
  • 支持ReactJSX語法
  • 支持ES6語法
  • 支持Less module
  • 支持EslintPrettierPre-commit hook
  • 支持HMR快速熱更新
  • 支持Antd按需引入與主題樣式覆蓋
  • 支持Proxy代理、alias別名
  • 兼容傳統瀏覽器
  • 開發啟動速度要夠快,以秒計算
  • 支持懶加載和chunk分割

介紹

前置條件之一

瀏覽器原生支持 ES 模塊。

特點

  • 基於原生 ES 模塊,即 <script type="module" >,做到快速加載
  • 使用 Esbuild 預構建依賴 (本地開發環境)
  • 使用 Rollup 打包代碼(線上生產環境)
  • HMR 是在原生 ESM 上執行的
  • 利用對 HTTP 頭信息的控制,優化緩存與重加載,高效率利用瀏覽器能力。
  • 開箱即用,內置多種支持,如:Typescript支持、JSX支持、CommonJSUMD兼容、css預處理器與css modules

vite對模塊的分類

  • 依賴
    • 在開發時不會變動的純 JavaScript。(如第三方依賴antdlodash等)
    • 在該場景采用具有優勢的 Esbuild 處理。(大量模塊的組件庫、CommonJS格式的文件等)
  • 源碼
    • 通常包含一些並非直接是 JavaScript 的文件,需要轉換,時常會被編輯。(JSXCSSVue/React組件等)
    • 會根據路由拆分代碼按需加載模塊
    • Vite 以 原生 ESM 方式提供源碼

概念

預構建:將有許多內部模塊的 ESM 依賴關系轉換為單個模塊,以提高后續頁面加載性能。簡單來說就是盡量合並與減少請求。

例如:當我們引入的一個第三方模塊依賴了大量其他模塊時,在不合並請求的情況下,會請求上百次不等,造成網絡擁塞影響性能,而通過預構建合並后只需要一個請求即可。(lodash-es 有超過個內置模塊!當我們執行 import { debounce } from 'lodash-es' 時,瀏覽器同時發出 600 多個 HTTP 請求!通過預構建 lodash-es 成為一個模塊,我們就只需要一個 HTTP 請求了!)

相較於傳統的 webpack 構建工具,先打包構建所有的依賴和項目代碼,然后再啟動開發服務器。Vite 則利用瀏覽器對 ESM 的支持,先啟動開發服務器,然后再根據代碼執行按需加載剩下所需的對應模塊。

因為緩存,在我們第二次啟動時幾乎可以做到秒開!非常的可怕~

官網圖

官網圖很清晰的描繪了區別:

Bundle based dev server

Native ESM based dev server

步驟

項目初始化

官方支持React模板預設有:reactreact-ts,因為我需要Typescript,所以直接用這個模板,省事了~

# npm 6.x
npm init @vitejs/app my-react-app --template react-ts

# npm 7+, 需要額外的雙橫線:
npm init @vitejs/app my-react-app -- --template react-ts

# yarn
yarn create @vitejs/app my-react-app --template react-ts

引入react三件套

這里有興趣的可以嘗試下 pnpm 包管理工具,安裝速度很快,不了解的可以查看pnpm官方文檔,相較於傳統的npmyarn工具都有很好的性能提升與使用體驗,這里不做過多介紹,放張圖大家體會下~

注:就目前我的使用情況來看大部分場景幾乎都沒問題的,不過還是存在一小部分問題。如:安裝precommit后Git hooks不生效等。

安裝速度比較

安裝依賴

# pnpm
pnpm add react react-dom react-router-dom
# or npm
npm i react react-dom react-router-dom

創建頁面

src目錄下創建pages目錄放置頁面組件模塊,然后我們簡單寫兩個頁面測試下:

// pages/Home/index.tsx
import React from 'react';

const Home: React.FC = () => <div> Home </div>;

export default Home;

// pages/About/index.tsx
import React from 'react';

const About: React.FC = () => <div> About </div>;

export default About;

修改文件App.tsx

// App.tsx
import React, { Suspense } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Home from './pages/Home'
import About from './pages/About'

const App = () => {
  return (
    <Suspense fallback={<span>loading</span>}>
      <Router>
        <Switch>
          <Route key="/home" path="/home" component={Home}></Route>
          <Route key="/about" path="/about" component={About}></Route>
        </Switch>
      </Router>
    </Suspense>
  );
};

export default App;

vite-react-app-1

vite-react-app-2

配置路由/界面

新建layouts組件,主要用於區別渲染登錄注冊頁面布局界面:

layouts/BasicLayout.tsxlayouts/UserLayout.tsx

這里就不一一做展示了,詳細代碼見倉庫,地址貼在下面了。

新建路由配置文件router/index.ts:

import React from 'react';

const Page404 = React.lazy(() => import('../pages/404'));
const Home = React.lazy(() => import('../pages/Home'));
const Login = React.lazy(() => import('../pages/User/Login'));
const Register = React.lazy(() => import('../pages/User/Register'));

const routes: IRoute[] = [
  {
    path: '/user',
    component: React.lazy(() => import('../layouts/UserLayout')),
    meta: {
      title: '用戶路由',
    },
    redirect: '/user/login',
    children: [],
  },
  {
    path: '/',
    component: React.lazy(() => import('../layouts/BasicLayout')),
    meta: {
      title: '系統路由',
    },
    redirect: '/home',
    children: [
      {
        path: '/home',
        meta: {
          title: '首頁',
          icon: 'home',
        },
        component: <Home />,
      },
      {
        path: '/about',
        meta: {
          title: '關於',
          icon: 'about',
        },
        component: <About />,
      },
    ],
  },
]

export default routes;

創建store狀態管理文件

react hooks誕生后,大部分場景使用hooksprops進行狀態管理基本可以滿足多數需求,少部分全局應用信息與用戶信息等需要全局狀態管理的,這里我覺得也不需要完整引入一個reduxmobx等這種庫。當然還要結合具體場景和公司技術棧確定,較大型和復雜的項目等視情況而定~

我這里使用了zustand做了簡單配置,使用起來也是比較的簡單,詳情參見官方文檔

// 創建store
import create from 'zustand'

const useStore = create(set => ({
  bears: 0,
  increasePopulation: () => set(state => ({ bears: state.bears + 1 })),
  removeAllBears: () => set({ bears: 0 })
}))
// 組件綁定
function BearCounter() {
  const bears = useStore(state => state.bears)
  return <h1>{bears} around here ...</h1>
}

function Controls() {
  const increasePopulation = useStore(state => state.increasePopulation)
  return <button onClick={increasePopulation}>one up</button>
}

引入Antd組件庫並配置按需加載

這里就不廢話了,直接展示如何在vite中配置antd的按需加載,首先我們安裝一個插件:

pnpm add vite-plugin-imp -D

然后在vite.config.ts文件的plugins中添加配置vitePluginImp

這里我們順勢再引入一個less-vars-to-js包,less-vars-to-js可以將less文件轉化為json鍵值對的形式,當然你也可以直接在modifyVars屬性后寫json鍵值對。這樣做的好處是可以把全局配置統一放到config文件進行管理,方便維護。

自定義覆蓋主題色

config/variables.less // @primary-color: '#ff7875';

import reactRefresh from '@vitejs/plugin-react-refresh';
import lessToJS from 'less-vars-to-js';
import path from 'path';
import { defineConfig } from 'vite';
// vite-plugin-imp 該插件按需加載存在部分樣式丟失的情況
// import vitePluginImp from 'vite-plugin-imp';
// 由於 vite 本身已按需導入了組件庫,因此僅樣式不是按需導入的,因此只需按需導入樣式即可。
import styleImport from 'vite-plugin-style-import';

const themeVariables = lessToJS(
  fs.readFileSync(path.resolve(__dirname, './config/variables.less'), 'utf8'),
);

export default defineConfig({
  base,
  plugins: [
    reactRefresh(),
    // 配置按需引入antd
    // vitePluginImp({
    //   libList: [
    //     {
    //       libName: 'antd',
    //       style: (name) => `antd/es/${name}/style/index.less`,
    //     },
    //   ],
    // }),
    styleImport({
      libs: [
        {
          libraryName: 'antd',
          esModule: true,
          resolveStyle: (name) => {
            return `antd/es/${name}/style/index`;
          },
        },
      ],
    }),
  ],
  css: {
    preprocessorOptions: {
      less: {
        // 支持內聯 JavaScript,支持 less 內聯 JS
        javascriptEnabled: true,
        // 重寫 less 變量,定制樣式
        modifyVars: themeVariables,
      },
    },
  }
})

vite-img-4

環境變量

方案一

通過 --mode 注入配置參數以匹配測試/開發環境等。

我們修改下package.json文件:

scripts: {
  "build:beta": "vite build --mode beta",
  "build:release": "vite build --mode release",
  "build:legacy ": "vite build --mode legacy ",
}

node環境下直接process.argv即可獲取到,我們可以在vite.config.ts中打印信息查看

// vite.config.ts
import {defineConfig} from 'vite'

const env = process.argv[process.argv.length - 1];
console.log('env:', env);

export default defineConfig({})

方案二

使用函數式寫法配置動態獲取環境變量等參數

首先在根目錄下創建.env文件

# port
VITE_PORT = 3100

# HTTP API
VITE_HTTP_API = http://127.0.0.1:8000

# title
VITE_APP_TITLE = Vite React App

然后我們調整下 vite.config.ts 文件

// 函數式配置
import { loadEnv } from 'vite';
import type { ConfigEnv, UserConfig } from 'vite';

export default ({ command, mode }: ConfigEnv): UserConfig => {
  const root = process.cwd();
  const env = loadEnv(mode, root);

  console.log('env', env);
  console.log('command', command);
  console.log('mode', mode);
}

組件內可通過import.meta.env獲取,我們可以在Home/index.tsx中打印信息查看

// Home/index.tsx
import React from 'react'
import { Button } from 'antd'

const Home: React.FC = () => {
  console.log('import.meta.env', import.meta.env)
  return <div>
    <Button type='primary'>Home</Button>
  </div>
}
export default Home

alias 別名設置

export default defineConfig({
    ...
  resolve: {
    alias: [
      { find: /^~/, replacement: path.resolve(__dirname, './') },
      { find: '@', replacement: path.resolve(__dirname, 'src') },
    ],
    // alias: {
    //   '~': path.resolve(__dirname, './'), // 根路徑
    //   '@': path.resolve(__dirname, 'src') // src 路徑
    // }
  }
  ...
})

proxy代理配置

export default defineConfig({
    ...
    server: {
    port: 8080, // 開發環境啟動的端口
    proxy: {
      '/api': {
        // 當遇到 /api 路徑時,將其轉換成 target 的值
        target: 'http://127.0.0.1:8080/api/',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ''), // 將 /api 重寫為空
      },
    }
  }
})

兼容傳統瀏覽器

vite默認只支持現代瀏覽器,即ES module,對於IE等老版本瀏覽器,可使用@vitejs/plugin-legacy插件在build時進行polyfill

yarn add @vitejs/plugin-legacy -D

// vite.config.js
import legacy from '@vitejs/plugin-legacy'

export default {
  plugins: [
    legacy({
      targets: ['ie >= 11'],
      additionalLegacyPolyfills: ['regenerator-runtime/runtime']
    })
  ]
}

Eslint、Prettier、Stylelint 代碼檢查與格式化

使用eslint這種代碼檢查工具是為了更好的規范代碼和寫法,列如:禁用var,建議使用constlet,以及配合TS使用時對各種類型規范等,對於代碼優化與后期維護都很方便。

而格式化對於多人協同開發時,統一代碼風格很有用。

VSCode 集成

插件規則遵循就近原則,會優先啟用本項目下的配置文件,當前配置會覆蓋全局配置,如果沒有就采用全局配置。

source.fixAll.eslint 開啟后可使編輯器按照eslint規則 auto fix

vscode 中安裝 EslintPrettier 插件,並開啟功能,推薦在全局配置(或當前工作目錄下)中添加:

  // file: vscode setting.json
  // onSave
  "editor.formatOnSave": true, //每次保存的時候自動格式化
  // eslint
  "eslint.alwaysShowStatus": true,  // 總是在 VSCode 顯示 ESLint 的狀態
  "eslint.quiet": true,             // 忽略 warning 的錯誤
  "editor.codeActionsOnSave": {     // 保存時使用 ESLint 修復可修復錯誤
      "source.fixAll": true,
      "source.fixAll.eslint": true
  },
  // prettier
  "[javascript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[javascriptreact]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[typescript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[typescriptreact]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[json]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[vue]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[html]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[css]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[less]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[scss]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[yaml]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },

為了方便配置,可以使用 VsCode插件 setting sync(該插件通過GitHub Gist ID 綁定,實現雲同步上傳下載),通過GitHub Gist ID雲同步配置和插件。

git commit 集成

git commit原理:

git在執行的過程會提供相關鈎子函數,如commitpush時,在項目目錄下的.git目錄下的hooks文件夾下存在相關git生命周期的鈎子函數配置,我們可以手動自己配置,只需刪除后綴.sample即可,不過因為這是本地文件,對於團隊協同開發同步配置不太友好,所以還是建議采用第三方庫統一管理。

hooks文件夾./.git/hooks/

vite-react-app-2

  1. 我們先在項目根目錄下創建.husky文件夾

  2. 然后添加文件pre-commit

    #!/bin/sh
     . "$(dirname "$0")/_/husky.sh"
     npx --no-install lint-staged
    
  3. package.json 中添加如下配置:

    script 配置 postinstall是為了確保在安裝依賴完成后,npm可以執行postinstall鈎子,使husky安裝配置文件到.husky文件下,npm在安裝執行的過程提供了一些生命周期鈎子,postinstall、prepare等。

    {
      "script": {
        "postinstall": "husky install",
        "lint:fix": "eslint --cache --ext .js,.jsx,.ts,.tsx --no-error-on-unmatched-pattern --quiet --fix ./src",
        "lint:style": "stylelint --fix \"src/**/*.less\" --syntax less",
      },
      "lint-staged": {
        "**/*.{js,jsx,tsx,ts,json}": [
          "npm run lint:fix",
          "git add --force"
        ],
        "**/*.{less}": [
          "npm run lint:style",
          "git add --force"
        ]
      },
    }
    
  4. 再安裝 huskylint-staged 依賴庫,

在提交代碼時,lint git 暫存區的代碼,若 lint 不通過則中斷提交,保證問題代碼不進入代碼倉庫。

目錄結構

├── dist                                // 默認的 build 輸出目錄
├── config                              // 全局配置文件
└── src                                 // 源碼目錄
    ├── assets                          // 公共的文件(如image、css、font等)
    ├── components                      // 項目組件
    ├── constants                       // 常量/接口地址等
    ├── layout                          // 全局布局
    ├── routes                          // 路由
    ├── store                           // 狀態管理器
    ├── utils                           // 工具庫
    ├── pages                           // 頁面模塊
        ├── Home                        // Home模塊,建議組件統一大寫開頭
        ├── ...
    ├── App.tsx                         // react頂層文件
    ├── main.ts                         // 項目入口文件
    ├── typing.d.ts                     // ts類型文件
├── .editorconfig                       // IDE格式規范
├── .env                                // 環境變量
├── .eslintignore                       // eslint忽略
├── .eslintrc                           // eslint配置文件
├── .gitignore                          // git忽略
├── .npmrc                              // npm配置文件
├── .prettierignore                     // prettierc忽略
├── .prettierrc                         // prettierc配置文件
├── .stylelintignore                    // stylelint忽略
├── .stylelintrc                        // stylelint配置文件
├── index.html                          // 項目入口文件
├── LICENSE.md                          // LICENSE
├── package.json                        // package
├── pnpm-lock.yaml                      // pnpm-lock
├── postcss.config.js                   // postcss
├── README.md                           // README
├── tsconfig.json                       // typescript配置文件
└── vite.config.ts                      // vite

項目地址

項目Github地址

本文博客地址

參考

vitejs

Vite 2.0 + React + Ant Design 4.0 搭建開發環境

zustand

如何為你的 Vue 項目添加配置 Stylelint

從零配置 Eslint + Prettier + husky + lint-staged 構建前端代碼工作流

ESLint 使用指南

深入淺出eslint——關於我學習eslint的心得

在 pre-commit 的鈎子中運行 npm script

@umijs/fabric


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM