Webpack + React + TypeScript 構(gòu)建一個(gè)標(biāo)準(zhǔn)化應(yīng)用

項(xiàng)目及工程化配置以及主要功能配置、規(guī)范化管理和使用細(xì)節(jié)應(yīng)該都沒(méi)啥問(wèn)題了其弊。

后面若發(fā)現(xiàn)構(gòu)建及配置問(wèn)題朱盐、以及可優(yōu)化的細(xì)節(jié)部分、包括功能的進(jìn)一步迭代也會(huì)使項(xiàng)目不斷更新完善的~

目前主要是做了規(guī)范及工程化的配置扑庞,后期打算開(kāi)發(fā)并開(kāi)源一個(gè)react-admin標(biāo)準(zhǔn)后臺(tái)管理系統(tǒng),用于實(shí)踐并鞏固一些前端技術(shù)框架與組件開(kāi)發(fā)等拒逮。

hello

效果圖

最簡(jiǎn)單的demo運(yùn)行效果

最終效果

背景

為什么要自己搭建一個(gè)webpack-react-ts的標(biāo)準(zhǔn)化項(xiàng)目罐氨?

原因之一

在很早之前的公司時(shí),項(xiàng)目實(shí)際開(kāi)發(fā)過(guò)程中產(chǎn)生過(guò)這種需求:

  1. 每次開(kāi)發(fā)新項(xiàng)目時(shí)希望有個(gè)完整功能滩援、配置好用栅隐、標(biāo)準(zhǔn)化規(guī)范的模板可以快速使用

  2. 完整功能:如果是使用create-react-app創(chuàng)建項(xiàng)目,屬于最簡(jiǎn)易版的一個(gè)實(shí)現(xiàn)了玩徊,各種loader租悄、pluginbabelwebpack配置缺失恩袱,作為現(xiàn)代前端開(kāi)發(fā)不可或缺的構(gòu)建工具泣棋,其對(duì)項(xiàng)目的大部分功能支持是必須的。

  3. 配置好用:webpack對(duì)各種插件的配置與集成畔塔,基本路由模塊潭辈、頁(yè)面模塊、組件模塊澈吨、store狀態(tài)管理模塊等基本常用功能的簡(jiǎn)要示例及demo把敢,要有一定的覆蓋率。

  4. 標(biāo)準(zhǔn)規(guī)范:這個(gè)可能是工程化構(gòu)建最重要的部分之一了棚辽,eslint技竟、prettier冰肴、stylelint屈藐、commitlint等lint規(guī)范和格式化以及test測(cè)試榔组,還有目錄結(jié)構(gòu)規(guī)范、命名規(guī)范等联逻。對(duì)于規(guī)范化的意義其重要性不言而喻搓扯,可讀性、可維護(hù)性包归、開(kāi)發(fā)效率锨推、bug率等可見(jiàn)一斑

原因之二

當(dāng)然網(wǎng)上肯定有很多優(yōu)秀的輪子供我們使用,他們有的功能更全公壤、有的細(xì)節(jié)更好换可,有的示例更完整。這都是我們學(xué)習(xí)的榜樣厦幅,對(duì)于其源碼閱讀和學(xué)習(xí)也是極為有益的沾鳄,不過(guò),始終避免不了一點(diǎn)确憨,看的再多都不如自己實(shí)踐來(lái)的有效果译荞,所以,這也是我考慮的原因之一休弃,為了自己實(shí)踐和操作來(lái)提升技術(shù)熟練度吞歼。

原因三

這也是源于在實(shí)際開(kāi)發(fā)過(guò)程中的需求,即塔猾,不同技術(shù)平臺(tái)及場(chǎng)景適應(yīng)于不同的技術(shù)框架和路線篙骡。

舉個(gè)栗子,

  1. 管理后臺(tái)系統(tǒng):這個(gè)多適用于PC端桥帆,使用單頁(yè)面應(yīng)用開(kāi)發(fā)医增,主流技術(shù)框架 ReactVue老虫、Angular

  2. 網(wǎng)站類(lèi):這類(lèi)項(xiàng)目一般追求美觀和SEO搜索的重要性叶骨,而不會(huì)像管理后臺(tái)系統(tǒng)一樣有很復(fù)雜的邏輯,更多的是一些動(dòng)效及UI交互祈匙,因?yàn)?code>SPA的首屏加載及SEO問(wèn)題忽刽,社區(qū)有成熟的服務(wù)端渲染SSR方案:nextnuxt夺欲,分別對(duì)應(yīng)reactvue技術(shù)棧跪帝,對(duì)于習(xí)慣了單頁(yè)面開(kāi)發(fā)的同學(xué)還是很友好的。

  3. 移動(dòng)端:輕量化些阅、輕量化伞剑、輕量化,重要的事情說(shuō)三遍市埋。手機(jī)端流量第一黎泣,項(xiàng)目越小越輕量性能越好加載也就越快恕刘,對(duì)于用戶(hù)體驗(yàn)也就越好。比較手機(jī)端一般對(duì)應(yīng)的都是C端用戶(hù)抒倚,不像管理后臺(tái)主要是給B端及公司內(nèi)部使用褐着。用戶(hù)體驗(yàn)要求是完全不一樣的。如托呕,移動(dòng)端版react——preact含蓉,又如sveltesolidjs等無(wú)虛擬DOM框架项郊,減少大量運(yùn)行時(shí)代碼馅扣。

所以,我認(rèn)為每個(gè)技術(shù)都有其對(duì)應(yīng)的優(yōu)勢(shì)和適合的場(chǎng)景着降,單一框架并不是完全適合梭哈到底的岂嗓。

這也是我開(kāi)發(fā)的node-cli自動(dòng)化初始化項(xiàng)目的一個(gè)框架模板之一。

接下來(lái)對(duì)各個(gè)不同技術(shù)方案及最佳實(shí)踐也會(huì)考慮構(gòu)建具有實(shí)戰(zhàn)意義的模板以供使用

主要方向大概分為:webpack+react/vue+typescriptwebpack方向鹊碍、vite+react/vue+typescriptvite方向厌殉、nuxt/next/egg/koa等服務(wù)端渲染SSR方向、svelte/solidjs等無(wú)虛擬DOM的輕量級(jí)框架方向侈咕、rollup/parcel等構(gòu)建工具方向公罕、esbuild/swc等新型編譯器與構(gòu)建工具的結(jié)合使用等、以及小程序/跨平臺(tái)等耀销。

有點(diǎn)飄啊楼眷,想法還挺多的??~

開(kāi)搞

目標(biāo)

  • 技術(shù)棧:Webpack5.x + React17.x + Antd4.x + TypeScript4.x + Less
  • 工程規(guī)范/格式化:Eslint + Stylelint + Prettier + Commitlint
  • ES6JSX熊尉,Babel支持
  • 支持HMR熱更新
  • 支持低版本瀏覽器兼容
  • 支持Antd按需加載罐柳、自定義主題、css module
  • 支持js狰住、css壓縮张吉、chunk拆分、Gzip

依賴(lài)

dependencies

"dependencies": {
  "antd": "^4.16.10", // 懂得都懂
  "axios": "^0.21.1", // 懂得都懂
  "clsx": "^1.1.1", // 條件處理 React className 類(lèi)名
  "core-js": "3", // 現(xiàn)代JS語(yǔ)法polyfills庫(kù)催植,用于兼容瀏覽器及ES6肮蛹、ES7語(yǔ)法
  "dayjs": "^1.10.6", // 日期處理庫(kù),比moment小很多创南,只有幾kb
  "history": "^5.0.1", // H5 history庫(kù)
  "lodash": "^4.17.21", // 非常全面的方法庫(kù)
  "react": "^17.0.2", // 懂得都懂
  "react-dom": "^17.0.2", // 懂得都懂
  "react-helmet-async": "^1.0.9", // 在react中優(yōu)雅的添加 HTML header 各種屬性
  "react-router-dom": "^5.2.0" // react 路由庫(kù)伦忠,經(jīng)典 react 三件套
}

devDependencies

"devDependencies": {
  // babel
  "@babel/cli": "^7.14.8", // 可以讓babel以cli的方式執(zhí)行  如:babel src --out-dir dist --watch
  "@babel/core": "^7.15.0", // babel 核心包
  "@babel/plugin-proposal-class-properties": "^7.14.5", // @babel/preset-env 插件已包含
  "@babel/plugin-syntax-dynamic-import": "^7.8.3", // 動(dòng)態(tài)導(dǎo)入、懶加載
  "@babel/plugin-transform-modules-commonjs": "^7.15.0", // 轉(zhuǎn)化成CommonJS 規(guī)范的代碼
  "@babel/plugin-transform-react-constant-elements": "^7.14.5", // React 常量元素轉(zhuǎn)換器 : 它會(huì)尋找不隨 props 改變的所有靜態(tài)元素稿辙,并將它們從渲染方法(或者無(wú)狀態(tài)函數(shù)式組件)中抽離出來(lái)昆码,以避免多余地調(diào)用 createElement
  "@babel/plugin-transform-react-inline-elements": "^7.14.5", // React 行內(nèi)元素轉(zhuǎn)換器 : 它會(huì)將所有 JSX 聲明(或 者 createElement 調(diào)用)替換成優(yōu)化過(guò)的版本,以便代碼可以更快執(zhí)行
  "@babel/plugin-transform-runtime": "^7.15.0", // 抽離提取 Babel的注入代碼,防止重復(fù)加載赋咽,減小體積
  "@babel/preset-env": "^7.15.0", // 提供的預(yù)設(shè)笔刹,允許我們使用最新的JavaScript
  "@babel/preset-react": "^7.14.5", // react 支持
  "@babel/preset-typescript": "^7.15.0", // typescript 支持
  "babel-plugin-dynamic-import-node": "^2.3.3", // 為node提供加載轉(zhuǎn)換 import => require
  "babel-plugin-import": "^1.13.3", // 按需引入、加載
  "babel-plugin-lodash": "^3.3.4", // 按需加載
  "babel-plugin-transform-react-remove-prop-types": "^0.4.24", // 從生產(chǎn)生成中刪除不必要的類(lèi)型

  // commitlint 是 git commit 執(zhí)行規(guī)則相關(guān)插件
  "@commitlint/cli": "^13.1.0", 
  "@commitlint/config-conventional": "^13.1.0",

  // react hot 配合webpack實(shí)現(xiàn)熱更新插件
  "react-refresh": "^0.10.0",
  "@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",

  // @types 開(kāi)頭的是對(duì)應(yīng)包的 TypeScript 類(lèi)型聲明
  "@types/enzyme": "^3.10.9",
  "@types/enzyme-adapter-react-16": "^1.0.6",
  "@types/enzyme-to-json": "^1.5.4",
  "@types/react-dom": "^17.0.9",
  "@types/react-helmet": "^6.1.2",
  "@types/react-router-dom": "^5.1.8",
  "@types/webpack-env": "^1.16.2",

  // enzyme 測(cè)試庫(kù)
  "enzyme": "^3.11.0",
  "enzyme-adapter-react-16": "^1.15.6",
  "enzyme-to-json": "^3.6.2",

  // eslint
  "eslint": "^7.32.0",
  "eslint-config-airbnb-typescript": "^12.3.1", // airbnb 規(guī)范
  "eslint-config-prettier": "^8.3.0", // 關(guān)閉所有不必要或可能與[Prettier]沖突的規(guī)則冬耿。
  "eslint-import-resolver-typescript": "^2.4.0", // 添加 ts 語(yǔ)法支持  eslint-plugin-import
  "eslint-import-resolver-webpack": "^0.13.1", // 支持 eslint-plugin-import 讀寫(xiě)模塊解析
  "eslint-plugin-import": "^2.23.4", // ES6+  import/export 語(yǔ)法支持
  "eslint-plugin-jsx-a11y": "^6.4.1", // JSX元素的可訪問(wèn)性規(guī)則的靜態(tài)AST檢查器 
  "eslint-plugin-prettier": "^3.4.0", // 以 eslint的規(guī)則運(yùn)行 prettier 格式化
  "eslint-plugin-react": "^7.24.0", // react 相關(guān)規(guī)則
  "eslint-plugin-react-hooks": "^4.2.0", // react-hooks 相關(guān)規(guī)則
  "eslint-plugin-redux-saga": "^1.2.1", // redux-saga 相關(guān)規(guī)則
  "@typescript-eslint/eslint-plugin": "^4.29.0", // 使 eslint 支持 typescript,.eslintrc.js 的 plugins 參數(shù)
  "@typescript-eslint/parser": "^4.29.0", // 使 eslint 支持 typescript 萌壳,.eslintrc.js 的 parser 參數(shù)

  // webpack
  "webpack": "^5.49.0",
  "webpack-bundle-analyzer": "^4.4.2", // 包依賴(lài)分析 可視化
  "webpack-cli": "^4.7.2", // cli
  "webpack-dev-middleware": "^5.0.0", // 中間件亦镶,可配合 express以服務(wù)的方式開(kāi)發(fā)使用
  "webpack-dev-server": "^3.11.2", // dev-server
  "webpack-hot-middleware": "^2.25.0", // 熱加載
  "webpack-pwa-manifest": "^4.3.0", // 生成 pwa 相關(guān)配置

  // webpack loader:解析對(duì)應(yīng)文件
  "babel-loader": "^8.2.2",
  "style-loader": "^3.2.1", // 添加 css 到 HTML
  "css-loader": "^6.2.0", // css加載器 處理 @import/url()
  "postcss-loader": "^6.1.1", // 處理 css
  "less-loader": "^10.0.1", // less => css
  "file-loader": "^6.2.0", // 通過(guò) import/require() 加載的圖片等解析為 url
  "html-loader": "^2.1.2", // 壓縮HTML
  "svg-url-loader": "^7.1.1",
  "url-loader": "^4.1.1",

  // webpack plugin
  "html-webpack-plugin": "^5.3.2", // 簡(jiǎn)化HTML文件的創(chuàng)建 ,配合webpack包含hash的bundle使用
  "mini-css-extract-plugin": "^2.2.0", // css 壓縮
  "terser-webpack-plugin": "^5.1.4", // 使用 terser 壓縮 js (terser 是一個(gè)管理和壓縮 ES6+ 的工具)
  "clean-webpack-plugin": "^4.0.0-alpha.0", // 用于刪除/清理生成的 build 文件 
  "compression-webpack-plugin": "^8.0.1", // Gzip壓縮

  // prettier 格式化
  "prettier": "^2.3.2",
  "pretty-quick": "^3.1.1", // 在更改的文件上運(yùn)行 prettier

  // stylelint css樣式規(guī)范
  "stylelint": "^13.13.1",
  "stylelint-config-recess-order": "^2.4.0", // 按照session和Bootstrap的方式對(duì)CSS屬性進(jìn)行排序袱瓮。
  "stylelint-config-standard": "^22.0.0", // 基本規(guī)范

  // 工具
  "husky": "^7.0.1", // 自動(dòng)配置 Git hooks 鉤子
  "lint-staged": "^11.1.2", // 對(duì)暫存的git文件運(yùn)行l(wèi)inter
  "rimraf": "^3.0.2", // 刪除cli缤骨,兼容不同平臺(tái) 
  "yargs": "^17.1.0", // 讀取命令參數(shù)

  // 其他
  "typescript": "^4.3.5",
  "less": "^4.1.1", // less 的解析庫(kù)
  "postcss": "^8.3.6", // 專(zhuān)門(mén)處理樣式的工具
  "postcss-nested": "^5.0.6", // 解析處理嵌套規(guī)則
  "autoprefixer": "^10.3.1", // 自動(dòng)生成各瀏覽器前綴 postcss 的一個(gè)插件
  "serve": "^12.0.0", // 本地啟動(dòng)一個(gè)服務(wù),可以查看靜態(tài)文件
}

目錄規(guī)劃

├── dist                                // 默認(rèn)的 build 輸出目錄
├── .husky                              // pre-commit hook
├── config                              // 全局配置文件及webpack配置文件
├── public                              // 靜態(tài)文件
├── test                                // 測(cè)試目錄
└── src                                 // 源碼目錄
    ├── assets                          // 公共的文件(如image尺借、css绊起、font等)
    ├── components                      // 項(xiàng)目組件
    ├── constants                       // 常量/接口地址等
    ├── layout                          // 全局布局
    ├── routes                          // 路由
    ├── store                           // 狀態(tài)管理器
    ├── utils                           // 工具庫(kù)
    ├── pages                           // 頁(yè)面模塊
        ├── Home                        // Home模塊,建議組件統(tǒng)一大寫(xiě)開(kāi)頭
        ├── ...
    ├── App.tsx                         // react頂層文件
    ├── main.ts                         // 項(xiàng)目入口文件
    ├── typing.d.ts                     // ts類(lèi)型文件
├── .editorconfig                       // IDE格式規(guī)范
├── .eslintignore                       // eslint忽略
├── .eslintrc                           // eslint配置文件
├── .gitignore                          // git忽略
├── .npmrc                              // npm配置文件
├── .prettierignore                     // prettierc忽略
├── .prettierrc                         // prettierc配置文件
├── .stylelintignore                    // stylelint忽略
├── .stylelintrc                        // stylelint配置文件
├── babel.config.js                     // babel配置文件
├── commitlint.config.js                // commit配置文件
├── LICENSE.md                          // LICENSE
├── package.json                        // package
├── postcss.config.js                   // postcss
├── README.md                           // README
├── setupEnzyme.ts                      // Enzyme測(cè)試配置文件
├── tsconfig.json                       // typescript配置文件

使用 npx create-react-app xxx --typescript 可以快速創(chuàng)建 TS 項(xiàng)目燎斩,我們可以基于創(chuàng)建完的項(xiàng)目再進(jìn)行一些自定義配置虱歪。

ES6+、JSX 栅表、TypeScript 支持

這里我們使用 babel 處理笋鄙,當(dāng)然對(duì)于 TypeScript 也可以使用 ts-loader 解析,這里就不做演示了怪瓶,網(wǎng)上也有很多對(duì)比測(cè)試

JavaScript 語(yǔ)法特性支持

  • 首先安裝 babel 相關(guān)依賴(lài): yarn add @babel/preset-env -D
  • 然后在 .babelrc.js 中添加預(yù)設(shè)
module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        // 這里可以配置相關(guān)瀏覽器兼容規(guī)則萧落,暫時(shí)我們先不管
        // corejs: 3, 
        // debug: true,
        // useBuiltIns: 'usage', // 開(kāi)啟瀏覽器兼容 polyfills,會(huì)根據(jù)browserslist配置洗贰,引入需要的庫(kù)找岖,需要安裝對(duì)應(yīng)版本的 core-js@3
      }
    ]
  ]
}

React的JSX語(yǔ)法支持

  • 安裝依賴(lài):yarn add @babel/preset-react -D
  • 然后在 .babelrc.js 中添加預(yù)設(shè)
module.exports = {
  presets: ['@babel/preset-env', '@babel/preset-react']
}

TypeScript 支持

  • 安裝依賴(lài):yarn add typescript @babel/preset-typescript -D
  • 然后在 .babelrc.js 中添加預(yù)設(shè)
module.exports = {
  presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript']
}
  • 接下來(lái)在 webpack 中配置 loader
module.exports = {
  module: {
    rules: [
      // .ts .tsx
      {
        test: /\.(ts|tsx)$/,
        use: 'babel-loader',
        exclude: /node_modules/,
      },
    ]
  }
}

Antd按需加載、主題定制

antd作為react的好基友敛滋,在國(guó)內(nèi)后臺(tái)系統(tǒng)開(kāi)發(fā)中幾乎快成為標(biāo)配了许布,我們以antd為例配置按需加載和主題定制化

babel配置按需加載

  • 安裝antd:yarn add antd
  • 安裝依賴(lài):yarn add babel-plugin-import -D
  • 然后在 .babelrc.js 中配置plugins
module.exports = {
  presets: [],
  plugins: [
    [
      'import',
      {
        libraryName: 'antd',
        libraryDirectory: 'es',
        style: true, // or 'css'
      },
      'antd',
    ]
  ]
}

webpack/loader配置antd主題

css-loader、style-loader 也是必須的

module.exports = {
  module: {
    rules: [
      // 處理 .css
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'],
      },
      // 處理 .less
      {
        test: /\.less$/,
        use: [
          'style-loader',
          'css-loader',
          // less-loader
          {
            loader: 'less-loader',
            options: {
              lessOptions: {
                // 替換antd的變量绎晃,去掉 @ 符號(hào)即可
                // https://ant.design/docs/react/customize-theme-cn
                modifyVars: {
                  'primary-color': '#1DA57A',
                },
                javascriptEnabled: true, // 支持js
              },
            },
          },
        ],
      },
    ]
  }
}

工程化規(guī)范配置

Eslint代碼檢查

  • 安裝ts解析器:yarn add @typescript-eslint/parser -D

  • 安裝相關(guān)拓展:yarn add eslint-config-airbnb-typescript eslint-plugin-prettier -D

  • 安裝相關(guān)插件:yarn add eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-jsx-a11y @typescript-eslint/eslint-plugin -D

  • 添加 .eslintrc.js

module.exports = {
  parser: '@typescript-eslint/parser',
  // 使用 airbnb 拓展插件規(guī)范相關(guān)庫(kù)
  // prettier 已內(nèi)置了許多相關(guān)插件
  extends: ['airbnb-typescript', 'prettier'],
  // 拓展和支持相關(guān)能力的插件庫(kù)
  plugins: ['prettier', 'react', 'react-hooks', 'jsx-a11y', '@typescript-eslint'],
}
  • ES6+規(guī)范 import/export 導(dǎo)入導(dǎo)出配置:yarn add eslint eslint-plugin-import eslint-import-resolver-typescript eslint-import-resolver-webpack -D
module.exports = {
  parser: '@typescript-eslint/parser',
  // 使用 airbnb 拓展插件規(guī)范相關(guān)庫(kù)
  // prettier 已內(nèi)置了許多相關(guān)插件
  extends: ['airbnb-typescript', 'prettier'],
  // 拓展和支持相關(guān)能力的插件庫(kù)
  plugins: ['prettier', 'react', 'react-hooks', 'jsx-a11y', '@typescript-eslint'],
  settings: {
    'import/parsers': {
      '@typescript-eslint/parser': ['.ts', '.tsx'],
    },
    'import/resolver': {
      webpack: {
        config: './config/webpack.base.js',
      },
      typescript: {
        alwaysTryTypes: true, // always try to resolve types under `<root>@types` directory even it doesn't contain any source code, like `@types/unist`
        directory: './tsconfig.json',
      },
    },
    'import/ignore': ['types'], // Weirdly eslint cannot resolve exports in types folder (try removing this later)
  }
}

Prettier 代碼格式化

  • Eslint配置中我們已經(jīng)添加prettier插件爹脾,支持以eslint的規(guī)范進(jìn)行格式化,并提供保存時(shí)存在沖突時(shí)的解決方案
  • 添加 .prettierrc箕昭,這里再定義下格式化基本風(fēng)格

yarn add prettier -D

{
  "printWidth": 100, 
  "tabWidth": 2,
  "useTabs": false,
  "semi": true,
  "singleQuote": true,
  "trailingComma": "all",
  "arrowParens": "avoid",
  "endOfLine": "auto"
}

Stylelint 樣式規(guī)范

  • 安裝依賴(lài):yarn add stylelint stylelint-config-recess-order stylelint-config-standard -D
  • 添加.stylelintrc.js
module.exports = {
  extends: ['stylelint-config-standard', 'stylelint-config-recess-order'],
};

針對(duì)以上代碼規(guī)范檢查灵妨,我們也應(yīng)該添加對(duì)應(yīng) ignore 文件以忽略非必要的檢查文件,以?xún)?yōu)化性能

如:.eslintignore

node_modules
dist

Commitlint 代碼提交規(guī)范

git hook 鉤子的實(shí)現(xiàn)落竹,這里我們使用 husky + lint-staged 方案

  • 安裝依賴(lài):yarn add husky lint-staged -D

git commit 規(guī)范泌霍,這里我們使用 @commitlint/config-conventional

  • 安裝依賴(lài):yarn add "@commitlint/cli @commitlint/config-conventional -D

這里也可以使用pretty-quick代替prettier格式化:yarn add pretty-quick -D,使得prettier格式化更簡(jiǎn)單易用

  • 根目錄下創(chuàng)建 .husky 文件夾與commitlint.config.js文件,并在.husky 文件夾下添加 precommit朱转、commit-msg蟹地、以及.gitignore

commitlint.config.js 用于配置 commitlint 相關(guān)規(guī)范

// git commit 規(guī)范
// <類(lèi)型>[可選的作用域]: <描述>

// # 主要type
// feat:     增加新功能
// fix:      修復(fù)bug

// # 特殊type
// docs:     只改動(dòng)了文檔相關(guān)的內(nèi)容
// style:    不影響代碼含義的改動(dòng),例如去掉空格藤为、改變縮進(jìn)怪与、增刪分號(hào)
// build:    構(gòu)造工具的或者外部依賴(lài)的改動(dòng),例如webpack缅疟,npm
// refactor: 代碼重構(gòu)時(shí)使用
// revert:   執(zhí)行g(shù)it revert打印的message

// # 暫不使用type
// test:     添加測(cè)試或者修改現(xiàn)有測(cè)試
// perf:     提高性能的改動(dòng)
// ci:       與CI(持續(xù)集成服務(wù))有關(guān)的改動(dòng)
// chore:    不修改src或者test的其余修改分别,例如構(gòu)建過(guò)程或輔助工具的變動(dòng)

module.exports = {
  extends: ['@commitlint/config-conventional'],
  // rules: [] // 可以自定義規(guī)則
};

precommit git提供的鉤子函數(shù),在該階段執(zhí)行eslint等驗(yàn)證規(guī)范存淫,再根據(jù)執(zhí)行結(jié)果判斷是否提交

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

# npx --no-install pretty-quick --staged
npx --no-install lint-staged

commit-msg 驗(yàn)證 git commit -m "xx" 提交的 message 信息格式和規(guī)范

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

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

.gitignore 作用是為了忽略 husky安裝產(chǎn)生的非項(xiàng)目代碼文件

_

package.json配置

"scripts": {
  // 這里換成 postinstall 也可以耘斩,npm提供的一個(gè)生命周期鉤子
  // 主要是為了我們?cè)诎惭b完 husky依賴(lài)后可以執(zhí)行husky腳本,使git hook鉤子生效
  "prepare": "husky install", 
  "commitlint": "commitlint --from=master",

  // lint 相關(guān)規(guī)則和規(guī)范執(zhí)行命令
  "lint:eslint:fix": "eslint --ext .ts,.tsx --no-error-on-unmatched-pattern --quiet --fix",
  "lint:style": "stylelint --fix \"src/**/*.less\" --syntax less",
  "lint": "npm run lint:eslint:fix && npm run lint:style",

  // prettier 格式化的一個(gè)拓展庫(kù)
  "pretty-quick": "pretty-quick",
},
"lint-staged": {
  "*.{ts,tsx}": [
    "npm run lint:eslint:fix",
    "git add --force"
  ],
  "*.{md,json}": [
    "pretty-quick --staged",
    "git add --force"
  ]
}

vscode 配置

  • 安裝 eslint桅咆、prettier 拓展插件
  • 添加 .vscode/settings.json 配置文件括授,或者全局設(shè)置 vscode settings.json
// 保存時(shí)格式化
"editor.formatOnSave": true, 
// 相當(dāng)于是 vscode 保存時(shí)調(diào)用的鉤子事件
"editor.codeActionsOnSave": {
  // 保存時(shí)使用 ESLint 修復(fù)可修復(fù)錯(cuò)誤
  "source.fixAll.eslint": true
}
// 針對(duì)對(duì)應(yīng)文件設(shè)置默認(rèn)格式化插件
"[javascript]": {
  "editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascriptreact]": {
  "editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
  "editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
  "editor.defaultFormatter": "esbenp.prettier-vscode"
},

HMR 熱更新配置

這里我們使用 react-refresh+webpack 方案

  • 安裝依賴(lài):yarn add react-refresh @pmmmwh/react-refresh-webpack-plugin -D
  • 添加webpack配置
const { HotModuleReplacementPlugin } = require('webpack');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');

module.exports = {
  devServer: {
    port: 3010, // 端口
    publicPath: '/', // 基礎(chǔ)路徑
    open: true, // 打開(kāi)頁(yè)面
    hot: true, // 開(kāi)啟熱更新
    historyApiFallback: true, // 把404響應(yīng)定位到 index.html
    // proxy:{} // 開(kāi)發(fā)環(huán)境,接口代理
  },
  plugins: [new HotModuleReplacementPlugin(), new ReactRefreshWebpackPlugin()]
}

生產(chǎn)環(huán)境打包壓縮岩饼、chunk拆分

  • 安裝css壓縮插件:yarn add mini-css-extract-plugin css-minimizer-webpack-plugin -D
  • 安裝js壓縮插件:yarn add terser-webpack-plugin -D
  • 安裝gzip壓縮插件:yarn add compression-webpack-plugin -D
  • 如果想查看對(duì)包模塊的可視化分析可使用插件:webpack-bundle-analyzer

css壓縮的webpack配置:

const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
// 支持動(dòng)態(tài)注入 link 標(biāo)簽到 html中
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  module: {
    rules: [
      // .css
      {
        test: /\.css$/,
        use: [
          // 該插件是為了把 CSS提取到單獨(dú)的文件中
          MiniCssExtractPlugin.loader, 
          'css-loader'
        ],
      },
      // less
      {
        test: /\.less$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
          'less-loader'
        ],
      },
    ]
  },
  plugins: [
    // 簡(jiǎn)化 HTML 文件的創(chuàng)建
    new HtmlWebpackPlugin({
      template: 'public/index.html',
    }),

    // MiniCss
    new MiniCssExtractPlugin({
      filename: "[name].css",
      chunkFilename: "[id].css",
    }),
  ],

  optimization: {
    minimizer: [
      // 壓縮
      new CssMinimizerPlugin(),
    ]
  }
}

js壓縮的webpack配置:

webpack5.x內(nèi)置有terser-webpack-plugin荚虚,不過(guò)如果想要自定義配置還是需要安裝的

// 使用 terser 壓縮 JavaScript
const TerserPlugin = require("terser-webpack-plugin");

module.exports = {
  optimization: {
    minimize: true, // 開(kāi)啟壓縮
    minimizer: [
      // 1. 簡(jiǎn)單使用默認(rèn)配置
      new TerserPlugin() // 使用插件
      // 2. 自定義配置一些參數(shù)
      new TerserPlugin({
        terserOptions: {
          output: {
            comments: false, // 在輸出中省略注釋
          },
          // ie8: true 開(kāi)啟 ie8 支持
        },
      }),
    ],
  },
}

chunk拆分

這里的可玩性還是比較高的,各種性能優(yōu)化啥的籍茧,有興趣的朋友可以研究研究曲管,我們這里只做簡(jiǎn)單使用配置??~

module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all', // 處理哪種chunk,可選值為:all硕糊、async院水、initial(all包括異步和非異步)
      maxInitialRequests: 10, // 入口點(diǎn)的最大并行請(qǐng)求數(shù)。
      minSize: 0, // 生成 chunk 的最小體積(以 bytes 為單位)
      // 緩存組简十,該組內(nèi)配置可繼承檬某、覆蓋splitChunks中的配置
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/, // [\\/] 是為了分別匹配 win、Unix系統(tǒng)路徑
          // 拆分 vendor螟蝙,自定義name(vendor默認(rèn)是合并為一個(gè) chunk)
          name(module) {
            // 把 npm 庫(kù)拆分獨(dú)立打包
            const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
            return `npm.${packageName.replace('@', '')}`;
          },
        },
      },
    }
  },
}

Gzip壓縮

const CompressionPlugin = require('compression-webpack-plugin');

module.exports = {
  plugins: [
    new CompressionPlugin({
      algorithm: 'gzip', // 壓縮算法
      test: /\.js$|\.css$|\.html$/,
      threshold: 10240, // 只壓縮大于 10240 bytes 的chunk
      minRatio: 0.8, // 只壓縮大于該值的 minRatio = Compressed Size / Original Size
    })
  ]
}

【圖 1】chunk拆分恢恼、gzip壓縮后的效果

壓縮產(chǎn)物

【圖 2】Analyzer可視化分析

Analyzer分析

總結(jié)

本文著重描述了項(xiàng)目的工程化配置,涉及相關(guān)代碼規(guī)范胰默、開(kāi)發(fā)環(huán)境搭建场斑、生產(chǎn)環(huán)境優(yōu)化等,旨在打造出一個(gè)可快速使用的現(xiàn)代化webpack+react+typescript模板牵署,以供在以后的實(shí)際業(yè)務(wù)場(chǎng)景需求中零成本使用漏隐。

目前這只算是最簡(jiǎn)陋的一個(gè)demo,對(duì)webpack相關(guān)的各種調(diào)優(yōu)與項(xiàng)目?jī)?yōu)化還有很多方向可研究奴迅。

接下來(lái)就是搞實(shí)際場(chǎng)景的業(yè)務(wù)組件的開(kāi)發(fā)青责、第三方開(kāi)源組件的集成使用、通用組件的使用和開(kāi)發(fā)封裝了~

項(xiàng)目地址:https://github.com/JS-banana/webpack-react-ts

告辭

參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市脖隶,隨后出現(xiàn)的幾起案子扁耐,更是在濱河造成了極大的恐慌,老刑警劉巖产阱,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件婉称,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡构蹬,警方通過(guò)查閱死者的電腦和手機(jī)王暗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)怎燥,“玉大人,你說(shuō)我怎么就攤上這事蜜暑☆硪Γ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵肛捍,是天一觀的道長(zhǎng)隐绵。 經(jīng)常有香客問(wèn)我,道長(zhǎng)拙毫,這世上最難降的妖魔是什么依许? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮缀蹄,結(jié)果婚禮上峭跳,老公的妹妹穿的比我還像新娘。我一直安慰自己缺前,他們只是感情好蛀醉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著衅码,像睡著了一般拯刁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上逝段,一...
    開(kāi)封第一講書(shū)人閱讀 51,115評(píng)論 1 296
  • 那天垛玻,我揣著相機(jī)與錄音,去河邊找鬼奶躯。 笑死帚桩,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的嘹黔。 我是一名探鬼主播朗儒,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了醉锄?” 一聲冷哼從身側(cè)響起乏悄,我...
    開(kāi)封第一講書(shū)人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎恳不,沒(méi)想到半個(gè)月后檩小,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡烟勋,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年规求,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片卵惦。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡阻肿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出沮尿,到底是詐尸還是另有隱情丛塌,我是刑警寧澤,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布畜疾,位于F島的核電站赴邻,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏啡捶。R本人自食惡果不足惜姥敛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望瞎暑。 院中可真熱鬧彤敛,春花似錦、人聲如沸了赌。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)揍拆。三九已至渠概,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間嫂拴,已是汗流浹背播揪。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留筒狠,地道東北人猪狈。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像辩恼,于是被迫代替她去往敵國(guó)和親雇庙。 傳聞我的和親對(duì)象是個(gè)殘疾皇子谓形,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容