BACK
Featured image of post Vben Admin 指南與 Vite 配置

Vben Admin 指南與 Vite 配置

Vue-Vben-Admin 是一個基於 Vue3.0、Vite、 Ant-Design-Vue、TypeScript 的後台解決方案,目標是為開發中大型項目提供開箱即用的解決方案。包括二次封裝組件、utils、hooks、動態菜單、權限校驗、按鈕級別權限控制等功能。項目會使用前端較新的技術棧,可以作為項目的啟動模版,以幫助你快速搭建企業級中後台產品原型。也可以作為一個示例,用於學習 vue3、vite、ts 等主流技術。該項目會持續跟進最新技術,並將其應用在項目中。

參考網站
參考網站

這是一篇完整的 2022 年連結優化指南。
因此,如果你想要取得權威網站的反向連結。
你將會在這篇新指南中,享受本文可操作的技巧。
讓我們往深入其中吧。


Vben Admin

點我跳過 Vben Admin,直接前往配置Vite

介紹

Vue-Vben-Admin 是一個基於 Vue3.0ViteAnt-Design-VueTypeScript 的後台解決方案,目標是為開發中大型項目提供開箱即用的解決方案。包括二次封裝組件、utils、hooks、動態菜單、權限校驗、按鈕級別權限控制等功能。項目會使用前端較新的技術棧,可以作為項目的啟動模版,以幫助你快速搭建企業級中後台產品原型。也可以作為一個示例,用於學習 vue3vitets 等主流技術。該項目會持續跟進最新技術,並將其應用在項目中。

文檔

本地運行文檔

如需本地運行文檔,請拉取代碼到本地。

1
2
3
4
5
6
7
8
# 拉取代碼
git clone https://github.com/vbenjs/vue-vben-admin-doc

# 安裝依賴
yarn

# 運行項目
yarn dev

需要掌握的基礎知識

本項目需要一定前端基礎知識,請確保掌握Vue 的基礎知識,以便能處理一些常見的問題。建議在開發前先學一下以下內容,提前了解和學習這些知識,會對項目理解非常有幫助:

模版

vue-vben-admin 完整版本。該版本主要是提供一些Demo示例及插件的使用集成方式,主要用於參考。如果對項目不是很熟悉,不建議在此基礎上進行開發,請使用下方提供的精簡版本。

vue-vben-admin 精簡版本。刪除了相關示例、無用文件及功能、依賴。可以根據自身需求安裝對應的依賴。因為使用的是 vite,依賴刪除不會導致相關組件或者 hook 發出警告。只在需要的時候安裝對應的庫即可。

vite 插件推薦

vite-plugin-mock - 用於本地及開發環境數據 mock
vite-plugin-html - 用於 html 模版轉換,可以在html文件內進行書寫模版語法
vite-plugin-style-import - 用於組件庫樣式按需引入
vite-plugin-imagemin - 用於打包壓縮圖片資源
vite-plugin-theme - 用於在線切換主題色/黑暗主題適配等主題相關配置
vite-plugin-compression - 用於打包輸出.gz|.br文件
vite-plugin-svg-icons - 快速生成 svg sprite

瀏覽器支持

本地開發推薦使用Chrome 最新版瀏覽器,不支持Chrome 80以下版本。

生產環境支持現代瀏覽器,不支持IE。


創建VbenLearn項目

使用Vite創建項目

vite官方文檔

1
2
3
4
5
6
7
8
# 官網上的創建項目命令
yarn create @vitejs/app

# 名稱
vben-learn

# vue-ts的模板
√ vue-ts

yarn報錯

文件名、目錄名或卷標語法不正確。報錯,參考鏈接


安裝 vscode 插件

安裝插件

VbenAdmin 官方推荐


獲取 Vben Admin

從 GitHub 獲取代碼

1
git clone https://github.com/vbenjs/vue-vben-admin.git

從 Gitee 獲取代碼

1
git clone https://gitee.com/annsion/vue-vben-admin.git

注意
Gitee 的代碼可能不是最新的

imagemin 依賴安裝失敗解決方法

由於 imagemin 在國內安裝困難,提供以下幾個解決方案:

  1. 使用 yarn 在 package.json 內配置(推薦,項目內已集成,前提是必須使用 yarn)
package.json
1
2
3
4
5
// ...
"resolutions": {
  "bin-wrapper": "npm:bin-wrapper-china"
}
// ...
  1. 使用 npm,在電腦 host 文件加上如下配置即可
etc/hosts
1
199.232.4.133   raw.githubusercontent.com

npm script

package.json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// ...
"scripts": {
    # 安裝依賴
    "bootstrap": "yarn install",
    # 運行項目
    "serve": "npm run dev",
    # 運行項目
    "dev": "vite",
    # 構建項目
    "build": "vite build && esno ./build/script/postBuild.ts",
    # 清空緩存後構建項目
    "build:no-cache": "yarn clean:cache && npm run build",
    # 生成打包分析,在 `Mac OS` 電腦上執行完成後會自動打開界面,在 `Window` 電腦上執行完成後需要打開 `./build/.cache/stats.html` 查看
    "report": "cross-env REPORT=true npm run build",
    # 類型檢查
    "type:check": "vue-tsc --noEmit --skipLibCheck",
    # 預覽打包後的內容(先打包在進行預覽)
    "preview": "npm run build && vite preview",
    # 直接預覽本地 dist 文件目錄
    "preview:dist": "vite preview",
    # 生成 ChangeLog
    "log": "conventional-changelog -p angular -i CHANGELOG.md -s",
    # 刪除緩存
    "clean:cache": "rimraf node_modules/.cache/ && rimraf node_modules/.vite",
    # 刪除 node_modules (`window` 系統手動刪除該目錄較慢,可以使用該命令來進行刪除)
    "clean:lib": "rimraf node_modules",
    # 執行 eslint 校驗,並修復部分問題
    "lint:eslint": "eslint \"{src,mock}/**/*.{vue,ts,tsx}\" --fix",
    # 執行 prettier 格式化(該命令會對項目所有代碼進行 prettier 格式化,請謹慎執行)
    "lint:prettier": "prettier --write --loglevel warn \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"",
    # 執行 stylelint 格式化
    "lint:stylelint": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
    "lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js",
    "lint:pretty": "pretty-quick --staged",
    # 對打包結果進行 gzip 測試
    "test:gzip": "http-server dist --cors --gzip -c-1",
    # 對打包目錄進行 brotli 測試
    "test:br": "http-server dist --cors --brotli -c-1",
    # 重新安裝依賴,見下方說明
    "reinstall": "rimraf yarn.lock && rimraf package.lock.json && rimraf node_modules && npm run bootstrap",
    "install:husky": "is-ci || husky install",
    # 生成圖標集,見下方說明
    "gen:icon": "esno ./build/generate/icon/index.ts",
    "postinstall": "npm run install:husky"
},
// ...

目錄說明

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
.
├── build                               # 打包腳本相關
│   ├── config                          # 配置文件
│   ├── generate                        # 生成器
│   ├── script                          # 腳本
│   └── vite                            # vite配置
├── mock                                # mock文件夾
├── public                              # 公共靜態資源目錄
├── src                                 # 主目錄
│   ├── api                             # 接口文件
│   ├── assets                          # 資源文件
│   │   ├── icons                       # icon sprite 圖標文件夾
│   │   ├── images                      # 項目存放圖片的文件夾
│   │   └── svg                         # 項目存放svg圖片的文件夾
│   ├── components                      # 公共組件
│   ├── design                          # 樣式文件
│   ├── directives                      # 指令
│   ├── enums                           # 枚舉/常量
│   ├── hooks                           # hook
│   │   ├── component                   # 組件相關hook
│   │   ├── core                        # 基礎hook
│   │   ├── event                       # 事件相關hook
│   │   ├── setting                     # 配置相關hook
│   │   └── web                         # web相關hook
│   ├── layouts                         # 佈局文件
│   │   ├── default                     # 默認佈局
│   │   ├── iframe                      # iframe佈局
│   │   └── page                        # 頁面佈局
│   ├── locales                         # 多語言
│   ├── logics                          # 邏輯
│   ├── main.ts                         # 主入口
│   ├── router                          # 路由配置
│   ├── settings                        # 項目配置
│   │   ├── componentSetting.ts         # 組件配置
│   │   ├── designSetting.ts            # 樣式配置
│   │   ├── encryptionSetting.ts        # 加密配置
│   │   ├── localeSetting.ts            # 多語言配置
│   │   ├── projectSetting.ts           # 項目配置
│   │   └── siteSetting.ts              # 站點配置
│   ├── store                           # 數據倉庫
│   ├── utils                           # 工具類
│   └── views                           # 頁面
├── test                                # 測試
│   └── server                          # 測試用到的服務
│       ├── api                         # 測試服務器
│       ├── upload                      # 測試上傳服務器
│       └── websocket                   # 測試ws服務器
├── types                               # 類型文件
├── vite.config.ts                      # vite配置文件
└── windi.config.ts                     # windcss配置文件

Vite配置多環境

說明:

就好像之前在Vue-Cli中的env一樣。

Vite官網說明

VbenAdmin官方說明

項目的環境變量配置位於項目根目錄下的 .env.env.development.env.production

1
2
3
4
.env                # 在所有的環境中被載入
.env.local          # 在所有的環境中被載入,但會被 git 忽略
.env.[mode]         # 只在指定的模式中被載入
.env.[mode].local   # 只在指定的模式中被載入,但會被 git 忽略

創建默認配置文件

根目錄下創建:.env文件

1
2
3
4
5
6
7
8
# 運行的端口
VITE_PORT = 3100

# 應用名稱
VITE_GLOB_APP_TITLE = Vben Admin

# 應用短名稱
VITE_GLOB_APP_SHORT_NAME = vue_vben_admin

創建生產配置文件

根目錄下創建:.env.production文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 是否使用mock
VITE_USE_MOCK = true

# 公共路徑
VITE_PUBLIC_PATH = /

# 是否刪除所有日誌打印
VITE_DROP_CONSOLE = true

# 是否啟用gzip或brotli壓縮。
# 可選:gzip | brotli | none
# 如果你需要多個表格,你可以使用`,`來分隔。
VITE_BUILD_COMPRESS = 'none'

# 應用基本接口地址
VITE_GLOB_API_URL=/api

# 文件上傳地址,可選
# 可以通過nginx轉發或直接寫入實際地址。
VITE_GLOB_UPLOAD_URL=/upload

# 接口前綴
VITE_GLOB_API_URL_PREFIX=

# 是否啟用圖像壓縮
VITE_USE_IMAGEMIN= true

#使用PWA
VITE_USE_PWA = false

# 是否與舊版瀏覽器兼容
VITE_LEGACY = false

創建開發配置文件

根目錄下創建:.env.development文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
VITE_PORT = 3100

# 是否使用mock
VITE_USE_MOCK = true

# 公共路徑
VITE_PUBLIC_PATH = /

# 跨域代理,你可以配置多個代理。
VITE_PROXY=[["/api","http://localhost:3000"],["/upload","http://localhost:3001/upload"]]
# VITE_PROXY=[["/api","https://vvbin.cn/test"]]

# 是否刪除所有日誌打印
VITE_DROP_CONSOLE = false

# 應用基本接口地址
VITE_GLOB_API_URL=/api

# 文件上傳地址,可選
VITE_GLOB_UPLOAD_URL=/upload

# 接口前綴
VITE_GLOB_API_URL_PREFIX=

測試配置

src\App.vue
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<template>
  <div>{{ vitePort }}</div>
</template>

<script lang="ts">
import { defineComponent, ref } from "vue";

export default defineComponent({
  name: "App",
  setup() {
    const vitePort = ref(import.meta.env.VITE_PORT);
    return {
      vitePort,
    };
  },
});
</script>

生產環境動態配置,可參考 Vben Admin 官方說明


配置TS

說明

我們使用Vite創建的項目時使用的是vue-ts模板,所以在創建項目的時候package.json就自帶了typescript。該依賴會編譯我們的ts文件。那麼是依賴我們就可以配置它。配置內容還是照搬Vben的,加上註釋和規則來源。

創建配置文件

根目錄下創建:tsconfig.json文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
{
  "compilerOptions": {
    // ↓ 指定ECMAScript目標版本,esnext為最新版本
    "target": "esnext",

    // ↓ 指定生成哪個模塊系統代碼,esnext為最新版本
    "module": "esnext",

    // ↓ 決定如何處理模塊。
    "moduleResolution": "node",

    // ↓ 啟用所有嚴格類型檢查選項。
    "strict": true,

    // ↓ 禁止對同一個文件的不一致的引用。
    "forceConsistentCasingInFileNames": true,

    // ↓ 允許從沒有設置默認導出的模塊中默認導入。這並不影響代碼的輸出,僅為了類型檢查。
    "allowSyntheticDefaultImports": true,

    // ↓ 禁用函數參數雙向協變檢查。
    "strictFunctionTypes": false,

    // ↓ 在 .tsx文件裡支持JSX
    "jsx": "preserve",

    // ↓ 解析非相對模塊名的基準目錄。查看 模塊解析文檔了解詳情。
    "baseUrl": ".",

    // ↓ 允許編譯javascript文件。
    "allowJs": true,

    // ↓ 生成相應的 .map文件。
    "sourceMap": true,
    "esModuleInterop": true,
    "resolveJsonModule": true,

    // ↓ 若有未使用的局部變量則拋錯。
    "noUnusedLocals": true,

    // ↓ 若有未使用的參數則拋錯。
    "noUnusedParameters": true,

    // ↓ 啟用實驗性的ES裝飾器。
    "experimentalDecorators": true,

    // ↓ 編譯過程中需要引入的庫文件的列表。
    "lib": ["dom", "esnext"],

    // ↓ 要包含的類型聲明文件名列表。
    "types": ["vite/client"],

    // ↓ 要包含的類型聲明文件路徑列表。
    "typeRoots": ["./node_modules/@types/", "./types"],
    "incremental": true,

    // ↓ 在表達式和聲明上有隱含的 any類型時報錯。
    "noImplicitAny": false,

    // ↓ 忽略所有的聲明文件( *.d.ts)的類型檢查。
    "skipLibCheck": true,

    // ↓ 模塊名到基於 baseUrl的路徑映射的列表。查看 模塊解析文檔了解詳情。
    "paths": {
      "/@/*": ["src/*"],
      "/#/*": ["types/*"]
    }
  },

  // ↓ 指定一個匹配列表(屬於自動指定該路徑下的所有ts相關文件)
  "include": [
    "src/**/*.ts",
    "src/**/*.d.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "types/**/*.d.ts",
    "types/**/*.ts",
    "mock/**/*.ts"
  ],

  // ↓ 指定一個排除列表(include的反向操作)
  "exclude": ["node_modules", "dist", "**/*.js"]
}

這裡配置完ESLint之後會報錯,說找不到vite/client。不用管它,當你執行完後面的ESLint-TS的時候就不會報錯了。


安裝 ESLint

說明

ESLint簡單的來說就是去判斷你的JS代碼寫的格式對不對的一個依賴。沒有它你的代碼也能運行,有了它你的代碼可以寫的更漂亮。 ESLint還支持插件,第三方框架會基於ESLint寫出自己的代碼檢查插件。比如Vue3對應eslint-plugin-vue

安裝 ESLint

1
yarn add eslint --dev

配置ESLint:根目錄下創建:.eslintrc.js文件。

這裡可以使用eslint的init。但是我不清楚Vben的初始化操作是怎麼選的,所以直接拷貝Vben的配置。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
module.exports = {
  // ↓ 默認情況下,ESLint 會在所有父級目錄裡尋找配置文件,一直到根目錄。如果你想要你所有項目都遵循一個特定的約定時,這將會很有用,但有時候會導致意想不到的結果。為了將 ESLint 限製到一個特定的項目,在你項目根目錄下的 package.json 文件或者 .eslintrc.* 文件裡的 eslintConfig 字段下設置 "root": true。 ESLint 一旦發現配置文件中有 "root": true,它就會停止在父級目錄中尋找。
  // ↓ 此項是用來告訴eslint找當前配置文件不能往父級查找
  root: true,

  // ↓ 指定你想啟用的環境
  env: {
    browser: true,
    node: true,
    es6: true,
  },

  // ↓ 設置解析器
  parser: "",

  // ↓ 解析器選項
  parserOptions: {},

  // ↓ 擴展項
  extends: [],

  // ↓ 自定義規則配置
  rules: {}
}

配置ESLint忽略文件:根目錄下創建:.eslintignore文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
*.sh
node_modules
*.md
*.woff
*.ttf
.vscode
.idea
dist
/public
/docs
.husky
.local
/bin
Dockerfile

安裝 eslint-plugin-vue

說明

ESLint是檢查JS代碼的依賴,那麼它怎麼去檢查Vue語法的文件呢?要完成這件事,我們就需要安裝vue官方開發的ESLint插件eslint-plugin-vue。這樣ESLint就知道該怎麼檢查vue的文件了。

怎麼理解eslint-plugin-vuevue-eslint-parser的關係呢?

ESLint 會對我們的代碼進行校驗,而 parser 的作用是將我們寫的代碼轉換為 ESTreeESLint 會對 ESTree 進行校驗。

vue-eslint-parser文檔上說是vue的模板解析器。 vue-eslint-parser的文檔中強調<template>標籤中的內容進行檢查。

那麼我的理解是vue-eslint-parservue文件轉換成ESTree。然後使用eslint-plugin-vue來檢查這個ESTree。查出的結果交給ESLint

vue-eslint-parser的官方說明:

This parser allows us to lint the <template> of .vue files. We can make mistakes easily on <template> if we use complex directives and expressions in the template. This parser and the rules of eslint-plugin-vue would catch some of the mistakes.
這個解析器允許我們對.vue文件的<template>進行檢查。如果我們在模板中使用了複雜的指令和表達式,我們很容易在<template>上出錯。這個解析器和eslint-plugin-vue的規則可以發現一些錯誤。

總之eslint-plugin-vuevue-eslint-parser需要一起使用,而且官網也有標示怎麼操作。

安裝兩個 npm 插件

1
yarn add eslint-plugin-vue vue-eslint-parser --dev

將插件配置進ESLint

配置ESLint:根目錄下修改:.eslintrc.js文件

.eslintrc.js
1
2
3
4
5
6
7
8
9
module.exports = {
    // ...
    parser: 'vue-eslint-parser',
    // ...
    extends: [
        'plugin:vue/vue3-recommended',
    ],
    // ...
}

安裝 @typescript-eslint

說明

這裡同理需要安裝 @typescript-eslint/eslint-plugin@typescript-eslint/parser

安裝TS的Lint

1
yarn add @typescript-eslint/eslint-plugin @typescript-eslint/parser --dev

將插件配置進ESLint

配置ESLint:根目錄下修改:.eslintrc.js文件

.eslintrc.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
module.exports = {
    // ...
    parserOptions: {
        parser: "@typescript-eslint/parser",
        ecmaVersion: 2020,
        sourceType: "module",
        jsxPragma: "React",
        ecmaFeatures: {
            jsx: true,
            tsx: true,
        },
    },
    extends: ["plugin:vue/vue3-recommended", "plugin:@typescript-eslint/recommended"],
    // ...
}

安裝 eslint-plugin-prettier

說明

prettier用來格式化代碼的。一般IDE都有prettier的插件,在保存的時候格式化代碼。那麼我們eslint-plugin-prettier就是通過JS來判斷代碼格式是否正確。 一般這很必要,因為不同的程序員有不同的IDE,不同的IDE有不同的prettier插件。但對於項目來說只有一個eslint-plugin-prettier

eslint-plugin-prettier插件依賴於prettier依賴。那麼eslint-config-prettier插件又是幹嘛的?

prettierESLint之間有些規則不一樣,因此是用於解決衝突得。eslint-config-prettierprettier一些規則默認關閉了。

安裝

1
yarn add prettier eslint-plugin-prettier eslint-config-prettier --dev

配置prettier

prettier的配置項

根目錄下創建:prettier.config.js文件

prettier.config.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
module.exports = {
    printWidth: 100,
    tabWidth: 2,
    useTabs: false,
    semi: true,
    vueIndentScriptAndStyle: true,
    singleQuote: true,
    quoteProps: 'as-needed',
    bracketSpacing: true,
    trailingComma: 'es5',
    jsxBracketSameLine: false,
    jsxSingleQuote: false,
    arrowParens: 'always',
    insertPragma: false,
    requirePragma: false,
    proseWrap: 'never',
    htmlWhitespaceSensitivity: 'strict',
    endOfLine: 'lf',
    rangeStart: 0
}

配置prettier忽略文件

根目錄下創建:.prettierignore文件

.prettierignore
1
2
3
4
5
6
7
8
9
/dist/*
.local
.output.js
/node_modules/**

**/*.svg
**/*.sh

/public/*

配置進ESLint

配置ESLint:修改根目錄下:.eslintrc.js文件

.eslintrc.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
module.exports = {
    // ...
    extends: [
        "plugin:vue/vue3-recommended",
        "plugin:@typescript-eslint/recommended",
        "prettier",
        "plugin:prettier/recommended",
    ],
    // ...
}

自定義ESLint檢查規則

說明

ESLint是用來檢查代碼的。又安裝了那麼多的插件。但是官方默認的方案有時不一樣符合我們的要求。我們需要自定義自己的規則。

修改規則主要是修改根目錄下.eslintrc.js文件的rules字段。

修改規則

配置ESLint:根目錄下:.eslintrc.js文件

.eslintrc.js
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
module.exports = {
    // ...
    rules: {
        // ↓ 禁止使用@ts-ignore來消除ESLint檢查
        '@typescript-eslint/ban-ts-ignore': 'off',

        // ↓ 在函數和類方法上需要顯式的返回類型
        '@typescript-eslint/explicit-function-return-type': 'off',

        // ↓ 禁止使用any類型
        '@typescript-eslint/no-explicit-any': 'off',

        // ↓ 除導入語句外,禁止使用require語句
        '@typescript-eslint/no-var-requires': 'off',

        // ↓ 禁止使用空函數
        '@typescript-eslint/no-empty-function': 'off',

        // ↓ 對自定義事件名稱強制使用特定的大小寫
        'vue/custom-event-name-casing': 'off',

        // ↓ 禁止定義前使用
        'no-use-before-define': 'off',

        // ↓ 在定義變量之前不允許使用變量
        '@typescript-eslint/no-use-before-define': 'off',

        // ↓ 禁止使用@ts-註解
        '@typescript-eslint/ban-ts-comment': 'off',

        // ↓ 禁止使用特定類型
        '@typescript-eslint/ban-types': 'off',

        // ↓ 禁止使用!後綴運算符進行非null斷言
        '@typescript-eslint/no-non-null-assertion': 'off',

        // ↓ 在導出的函數和類的公共類方法上需要顯式的返回值和參數類型
        '@typescript-eslint/explicit-module-boundary-types': 'off',

        // ↓ 禁止使用未使用的變量
        '@typescript-eslint/no-unused-vars': [
            'error',
            {
                argsIgnorePattern: '^h$',
                varsIgnorePattern: '^h$',
            },
        ],

        // ↓ 禁止使用未使用的變量
        'no-unused-vars': [
            'error',
            {
                argsIgnorePattern: '^h$',
                varsIgnorePattern: '^h$',
            },
        ],

        // ↓ 在函數括號前需要或不允許有空格
        'space-before-function-paren': 'off',

        // ↓ 強制屬性順序
        'vue/attributes-order': 'off',

        // ↓ 強制每個組件應位於其自己的文件中
        'vue/one-component-per-file': 'off',

        // ↓ 在標籤的右括號之前要求或不允許換行
        'vue/html-closing-bracket-newline': 'off',

        // ↓ 強制每行的最大屬性數
        'vue/max-attributes-per-line': 'off',

        // ↓ 在多行元素的內容之前和之後需要換行
        'vue/multiline-html-element-content-newline': 'off',

        // ↓ 在單行元素的內容之前和之後需要換行
        'vue/singleline-html-element-content-newline': 'off',

        // ↓ 在模板中的自定義組件上實施屬性命名樣式
        'vue/attribute-hyphenation': 'off',

        // ↓ 需要道具的默認值
        'vue/require-default-prop': 'off',

        // ↓ 實施自我封閉的風格
        // 'vue/html-self-closing': 'off',
        'vue/html-self-closing': [
            'error',
            {
                html: {
                    void: 'always',
                    normal: 'never',
                    component: 'always',
                },
                svg: 'always',
                math: 'always'
            }
        ]
    }
}

vben項目配置git忽略文件

說明

這個應該是項目一上來就要處理的,但是我手撕Vben的時候,按照配置文件的順序下來,到現在才處理了。

創建Git忽略文件

根目錄下創建:.gitignore文件

.gitignore
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
node_modules
.DS_Store
dist
dist-ssr
*.local

# other
.npmrc
.cache
test/upload-server/static

.local
# local env files
.env.local
.env.*.local

# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*

# Editor directories and files
.idea
# .vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

安裝style-lint

說明

這次需要安裝四個插件stylelintstylelint-config-standardstylelint-config-prettierstylelint-order

  • stylelint是對我們編寫的樣式進行檢查的插件。
  • stylelint-config-standardstylelint擴展的檢查標準庫。
  • stylelint-config-prettier是用來解決衝突的(估計又和ESLint衝突了,參考之前的prettier)。
  • stylelint-order是檢查我們樣式編寫順序的。

參考連結

安裝

1
yarn add stylelint stylelint-config-standard stylelint-config-prettier stylelint-order --dev

配置stylelint

根目錄下創建:stylelint.config.js文件

stylelint.config.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
module.exports = {
    // ↓ 這個好像是沒有用的,但是VbenAdmin是這麼寫的
    root: true,

    // ↓ 插件
    plugins: ['stylelint-order'],

    // ↓ 擴展
    extends: ['stylelint-config-standard', 'stylelint-config-prettier'],

    // ↓ 自定義規則
    rules: {},

    // ↓ 忽略檢查的文件
    ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts']
}

配置自定義樣式規則

根目錄下修改:stylelint.config.js文件

stylelint.config.js
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
module.exports = {
    // ...
    rules: {
        // ↓ 禁止使用未知的偽類選擇器。
        'selector-pseudo-class-no-unknown': [
            true,
            {
                ignorePseudoClasses: ['global']
            }
        ],
        
        // ↓ 禁止使用未知規則。
        'at-rule-no-unknown': [
            true,
            {
                ignoreAtRules: ['function', 'if', 'each', 'include', 'mixin']
            }
        ],
        
        // ↓ 禁止空來源。
        'no-empty-source': null,
        
        // ↓ 禁止使用無效的命名網格區域。
        'named-grid-areas-no-invalid': null,
        
        // ↓ 要求或不允許使用Unicode字節順序標記。
        'unicode-bom': 'never',
        
        // ↓ 禁止較低特異性的選擇器在覆蓋較高特異性的選擇器之後出現。
        'no-descending-specificity': null,
        
        // ↓ 禁止在字體系列名稱列表中缺少通用系列。
        'font-family-no-missing-generic-family-keyword': null,
        
        // ↓ 在聲明的冒號後面需要一個空格或禁止空格。
        'declaration-colon-space-after': 'always-single-line',
        
        // ↓ 在聲明的冒號之前需要一個空格或禁止使用空格。
        'declaration-colon-space-before': 'never',
        
        // ↓ 在聲明塊內要求或不允許尾隨分號。
        'declaration-block-trailing-semicolon': 'always',
        
        // ↓ 在規則之前要求或禁止使用空行。
        'rule-empty-line-before': [
            'always',
            {
                ignore: ['after-comment', 'first-nested'],
            }
        ],
        
        // ↓ 禁止使用未知單位。
        'unit-no-unknown': [true, { ignoreUnits: ['rpx'] }],
        // Specify the alphabetical order of the attributes in the declaration block
        
        // ↓ 樣式順序
        'order/properties-order': [
            'position',
            'top',
            'right',
            'bottom',
            'left',
            'z-index',
            'display',
            'float',
            'width',
            'height',
            'max-width',
            'max-height',
            'min-width',
            'min-height',
            'padding',
            'padding-top',
            'padding-right',
            'padding-bottom',
            'padding-left',
            'margin',
            'margin-top',
            'margin-right',
            'margin-bottom',
            'margin-left',
            'margin-collapse',
            'margin-top-collapse',
            'margin-right-collapse',
            'margin-bottom-collapse',
            'margin-left-collapse',
            'overflow',
            'overflow-x',
            'overflow-y',
            'clip',
            'clear',
            'font',
            'font-family',
            'font-size',
            'font-smoothing',
            'osx-font-smoothing',
            'font-style',
            'font-weight',
            'hyphens',
            'src',
            'line-height',
            'letter-spacing',
            'word-spacing',
            'color',
            'text-align',
            'text-decoration',
            'text-indent',
            'text-overflow',
            'text-rendering',
            'text-size-adjust',
            'text-shadow',
            'text-transform',
            'word-break',
            'word-wrap',
            'white-space',
            'vertical-align',
            'list-style',
            'list-style-type',
            'list-style-position',
            'list-style-image',
            'pointer-events',
            'cursor',
            'background',
            'background-attachment',
            'background-color',
            'background-image',
            'background-position',
            'background-repeat',
            'background-size',
            'border',
            'border-collapse',
            'border-top',
            'border-right',
            'border-bottom',
            'border-left',
            'border-color',
            'border-image',
            'border-top-color',
            'border-right-color',
            'border-bottom-color',
            'border-left-color',
            'border-spacing',
            'border-style',
            'border-top-style',
            'border-right-style',
            'border-bottom-style',
            'border-left-style',
            'border-width',
            'border-top-width',
            'border-right-width',
            'border-bottom-width',
            'border-left-width',
            'border-radius',
            'border-top-right-radius',
            'border-bottom-right-radius',
            'border-bottom-left-radius',
            'border-top-left-radius',
            'border-radius-topright',
            'border-radius-bottomright',
            'border-radius-bottomleft',
            'border-radius-topleft',
            'content',
            'quotes',
            'outline',
            'outline-offset',
            'opacity',
            'filter',
            'visibility',
            'size',
            'zoom',
            'transform',
            'box-align',
            'box-flex',
            'box-orient',
            'box-pack',
            'box-shadow',
            'box-sizing',
            'table-layout',
            'animation',
            'animation-delay',
            'animation-duration',
            'animation-iteration-count',
            'animation-name',
            'animation-play-state',
            'animation-timing-function',
            'animation-fill-mode',
            'transition',
            'transition-delay',
            'transition-duration',
            'transition-property',
            'transition-timing-function',
            'background-clip',
            'backface-visibility',
            'resize',
            'appearance',
            'user-select',
            'interpolation-mode',
            'direction',
            'marks',
            'page',
            'set-link-source',
            'unicode-bidi',
            'speak'
        ]
    }
    // ...
}

配置stylelint忽略文件

根目錄下創建:.stylelintignore文件

.stylelintignore
1
2
/dist/*
/public/*

配置Yarn自動清除功能

說明

實現每一次install之後、add之後、yarn autoclean --force之後。從程序包依賴項中清除並刪除不必要的文件。

參考鏈接:yarn autoclean-官方說明

初始化

1
yarn autoclean --init

執行完命令之後,Yarn就會自動在根目錄下創建一個.yarnclean文件,這樣就可以了。


安裝PostCSS

說明

我們需要安裝兩個庫postcssautoprefixer。要現實的效果就是我們編寫一般的樣式,渲染頁面的dom節點有面向各種瀏覽器廠商的樣式。

網上說postcss類似一個平台,上面有很多插件,autoprefixer就其中一個。

我以我的看法來解釋一下:postcss就像一個管子,autoprefixer是裡面的過濾器。你寫的css從管子中流過,經過他們的處理就出現了不同的結果。

參考連結:

安裝

1
yarn add postcss autoprefixer --dev

配置postcss

根目錄下:postcss.config.js

postcss.config.js
1
2
3
4
5
module.exports = {
    plugins: {
        autoprefixer: {}
    }
}

解析VbenAdmin中的script

說明

VbenAdmin官方文檔-npm Script

一下腳本直接添加在根目錄下的package.jsonscripts對象中即可。也可以直接照搬VbenAdmin的。

bootstrap為例。如下:

1
2
3
yarn bootstrap
# or
# npm run bootstrap

bootstrap

代碼:

package.json
1
2
3
4
5
"scripts": {
    // ...
    "bootstrap": "yarn install",
    // ...
}

作用:使用yarn安裝所有依賴。

serve

代碼:

package.json
1
2
3
4
5
"scripts": {
    // ...
    "serve": "npx --max_old_space_size=4096 vite",
    // ...
}

作用:以開發環境運行項目,並設置node的運行內存為4G

dev

代碼:

package.json
1
2
3
4
5
"scripts": {
    // ...
    "dev": "npx --max_old_space_size=4096 vite",
    // ...
}

作用:同serve

build

代碼:

package.json
1
2
3
4
5
"scripts": {
    // ...
    "build": "vite build",
    // ...
}

作用:這裡先不管。先使用原來創建項目的"build": "vite build"。後面完成了vite配置之後再處理這個腳本。

build:no-cache

代碼:

package.json
1
2
3
4
5
"scripts": {
    // ...
    "build:no-cache": "yarn clean:cache && npm run build",
    // ...
}

作用:清空緩存後構建。

report

作用:生成打包分析,這裡先不管,等後面配置vite再說。

preview

代碼:

package.json
1
2
3
4
5
"scripts": {
    // ...
    "preview": "npm run build && vite preview",
    // ...
}

作用:先打包在進行預覽,使用vite訪問dist目錄。

preview:dist

代碼:

package.json
1
2
3
4
5
"scripts": {
    // ...
    "preview:dist": "vite preview",
    // ...
}

作用:直接預覽本地 dist 文件目錄。

log

詳情查閱:使用conventional-changelog-cli生成變更記錄

作用:用來生產Git提交記錄的。

clean:cache

作用:刪除緩存,刪除node_modules/.cache/node_modules/.vite下的文件,由於刪除命令依賴還沒安裝,所以後面再講。

clean:lib

作用:清除依賴,刪除node_modules文件夾,由於刪除命令依賴還沒安裝,所以後面再講。

lint:eslint

代碼:

package.json
1
2
3
4
5
"scripts": {
    // ...
    "lint:eslint": "eslint \"{src,mock}/**/*.{vue,ts,tsx}\" --fix",
    // ...
}

作用:執行 eslint 校驗,該命令會對項目的srcmock目錄下的vuetstsx文件進行 eslint 校驗,並修復部分問題。
參考連結:eslint命令行說明

lint:prettier

代碼:

package.json
1
2
3
4
5
"scripts": {
    // ...
    "lint:prettier": "prettier --write --loglevel warn \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"",
    // ...
}

作用:執行 prettier 格式化代碼。該命令會對項目所有代碼 進行 prettier 格式化。謹慎執行。

如果你的vsCode安裝了prettier插件。那麼prettier插件就會讀取根目錄下的prettier.config.js文件。當你保存的時候就會進行格式化文件。
但是過是別人提交上來的文件,你還去點開它的文件一個一個保存麼?所以執行這個命令,可以將src目錄下,所有的文件格式化。

參考連結:prettier命令行說明

lint:stylelint

代碼:

package.json
1
2
3
4
5
"scripts": {
    // ...
    "lint:stylelint": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
    // ...
}

作用:校驗所有文件的樣式格式,並嘗試修復。

參考連結:stylelint命令行说明

lint:ls-lint

代碼:

package.json
1
2
3
4
5
"scripts": {
    // ...
    "lint:ls-lint": "ls-lint",
    // ...
}

作用:校驗所有文件命名是否正確。

lint:lint-staged

需先創建lint-staged的配置文件

代碼:

package.json
1
2
3
4
5
"scripts": {
    // ...
    "lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js",
    // ...
}

作用:對Git暫存的文件進行lint檢查。

lint:pretty

需先創建lint-staged的配置文件

代碼:

package.json
1
2
3
4
5
"scripts": {
    // ...
    "lint:pretty": "pretty-quick --staged",
    // ...
}

作用:對Git暫存文件進行pretty的操作。

test:gzip

作用:這個後面再說,http-server的npm首頁。

test:br

作用:這個後面再說,http-server的npm首頁。

reinstall

作用:重新安裝依賴,該命令會先刪除 node_modules、yarn.lock、package.lock.json後在進行依賴重新安裝,速度會明顯變慢。

由於刪除命令依賴還沒安裝,所以後面再講。

install:husky

代碼:

package.json
1
2
3
4
5
"scripts": {
    // ...
    "install:husky": "is-ci || husky install",
    // ...
}

作用:這個是用來初始化husky的。啟用Git掛鉤

說明:如果不是CI服務器,就啟動Git鉤子。

gen:icon

作用:生成圖標的,後面再說。

postinstall

需先配置 husky

代碼:

package.json
1
2
3
4
5
"scripts": {
    // ...
    "postinstall": "npm run install:husky",
    // ...
}

作用:執行install:husky命令。這個命令會自動啟動Git鉤子,該操作參考:手動初始化huskyYarn2下怎麼自動啟動Git鉤子函數

彙總

package.json除腳本、運行依賴、開發依賴外的字段參考官方文檔

yarn -v的腳本是後面要說的腳本。

package.json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
    // ...
    "scripts": {
        "bootstrap": "yarn install",
        "serve": "npx --max_old_space_size=4096 vite",
        "dev": "npx --max_old_space_size=4096 vite",
        "build": "vite build",
        "build:no-cache": "yarn clean:cache && npm run build",
        "report": "yarn -v",
        "preview": "npm run build && vite preview",
        "preview:dist": "vite preview",
        "log": "yarn -v",
        "clean:cache": "yarn -v",
        "clean:lib": "yarn -v",
        "lint:eslint": "eslint \"{src,mock}/**/*.{vue,ts,tsx}\" --fix",
        "lint:prettier": "prettier --write --loglevel warn \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"",
        "lint:stylelint": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
        "lint:ls-lint": "ls-lint",
        "lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js",
        "lint:pretty": "pretty-quick --staged",
        "test:gzip": "yarn -v",
        "test:br": "yarn -v",
        "reinstall": "yarn -v",
        "install:husky": "is-ci || husky install",
        "gen:icon": "yarn -v",
        "postinstall": "npm run install:husky"
    },
    // ...
}

安裝rimraf

說明

由於手動刪除文件夾太慢了,需要實現腳本刪除文件。

參考連結

rimraf的npm首頁

rimrafnpm首頁說明:
The UNIX command rm -rf for node.
Install with npm install rimraf, or just drop rimraf.js somewhere.

翻譯:

node版的Unix系統命令rm -rf
npm安裝rimraf,或者直接把rimraf.js放在某個地方。
雖然這裡可以通過import使用函數刪除文件,但是我們主要是使用它的命令行蛤。

詳情查看:rimraf的npm首頁-Cil說明

安裝

1
yarn add rimraf --dev

重寫腳本

就是根目錄下package.jsonscripts字段。

clean:cache

package.json
1
2
3
4
5
"scripts": {
    // ...
    "clean:cache": "rimraf node_modules/.cache/ && rimraf node_modules/.vite",
    // ...
}

目的:刪除緩存,刪除node_modules/.cache/node_modules/.vite下的文件。

clean:lib

package.json
1
2
3
4
5
"scripts": {
    // ...
    "clean:lib": "rimraf node_modules",
    // ...
}

目的:清除依賴,刪除node_modules文件夾。

reinstall

package.json
1
2
3
4
5
"scripts": {
    // ...
    "reinstall": "rimraf yarn.lock && rimraf package.lock.json && rimraf node_modules && npm run bootstrap",
    // ...
}

目的:重新安裝依賴,該命令會先刪除 node_modulesyarn.lockpackage.lock.json後在進行依賴重新安裝,速度會明顯變慢。


使用husky規范代碼提交

說明

使用Git提交代碼的時候,需要對暫存的代碼進行如下操作:

  • 進行lint操作。
  • commit的消息進行格式化檢查。
  • 進行prettier操作。

需要安裝的依賴有:huskylint-staged@commitlint/cli@commitlint/config-conventionalpretty-quickis-ci

  • husky:可以在Git的鉤子函數中執行腳本。
  • lint-staged:針對暫存文件進行lint操作。
  • @commitlint/cli:對commit的消息進行格式檢查。
  • @commitlint/config-conventional:commit的消息檢查格式傳統配置,對應還有很多其他配置,比如angular的提交規範@commitlint/config-angular
  • pretty-quick:針對暫存文件進行prettier操作。
  • is-cihusky好像不能在ci環境下執行,這個依賴是用來判斷是不是ci環境的。

參考連結

安裝

1
yarn add husky lint-staged @commitlint/cli @commitlint/config-conventional pretty-quick is-ci --dev

配置commitlint

在根目錄下創建commitlint.config.js文件。

commitlint.config.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
module.exports = {
    // ↓ 忽略包含init的提交消息
    ignores: [(commit) => commit.includes('init')],
    
    // ↓ 按照传统消息格式来验证
    extends: ['@commitlint/config-conventional'],
    
    // ↓ 这里是自定义解析器,看不太懂,直接搬代码吧
    // ↓ https://commitlint.js.org/#/reference-configuration?id=parser-presets
    parserPreset: {
        parserOpts: {
            headerPattern: /^(\w*|[\u4e00-\u9fa5]*)(?:[\(\(](.*)[\)\)])?[\:\:] (.*)/,
            headerCorrespondence: ['type', 'scope', 'subject'],
            referenceActions: [
                'close',
                'closes',
                'closed',
                'fix',
                'fixes',
                'fixed',
                'resolve',
                'resolves',
                'resolved'
            ],
            issuePrefixes: ['#'],
            noteKeywords: ['BREAKING CHANGE', '不兼容变更'],
            fieldPattern: /^-(.*?)-$/,
            revertPattern: /^Revert\s"([\s\S]*)"\s*This reverts commit (\w*)\./,
            revertCorrespondence: ['header', 'hash'],
            warn() {},
            mergePattern: null,
            mergeCorrespondence: null
        }
    },
    
    // ↓ 自定义提交消息规则
    rules: {
        // ↓ body以空白行开头
        'body-leading-blank': [2, 'always'],
        
        // ↓ footer以空白行开头
        'footer-leading-blank': [1, 'always'],
        
        // ↓ header的最大长度
        'header-max-length': [2, 'always', 108],
        
        // ↓ subject为空
        'subject-empty': [2, 'never'],
        
        // ↓ type为空
        'type-empty': [2, 'never'],
        
        // ↓ type的类型
        'type-enum': [
            2,
            'always',
            [
                'feat',
                'fix',
                'perf',
                'style',
                'docs',
                'test',
                'refactor',
                'build',
                'ci',
                'chore',
                'revert',
                'wip',
                'workflow',
                'types'
            ]
        ]
    }
};

配置husky

啟動Git鉤子

1
npx husky install

這個時候就在根目錄下創建了.husky目錄了。 vben將裡面的一個文件夾刪除了。

修復YarnOnWindowsBug

官方參考連結

在帶有Git Bash(stdin is not a tty)的Windows上使用Yarn時,Git掛鉤可能會失敗。如果您有Windows用戶,則強烈建議添加以下解決方法。

  1. 創建.husky/common.sh
1
2
3
4
5
6
7
8
command_exists () {
  command -v "$1" >/dev/null 2>&1
}

# Workaround for Windows 10, Git Bash and Yarn
if command_exists winpty && test -t 1; then
  exec < /dev/tty
fi
  1. 在使用Yarn運行命令的地方將其來源:
1
2
3
4
5
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
. "$(dirname "$0")/common.sh"

yarn ...

這裡是yarn命令的地方要這樣用,其他地方要不要無所謂。第二步可以先不管。

創建lint-staged的配置文件

  1. lint:lint-staged
package.json
1
2
3
"scripts": {
    "lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js"
}

作用:對Git暫存的文件進行lint檢查。

  1. lint:pretty
package.json
1
2
3
"scripts": {
    "lint:pretty": "pretty-quick --staged"
}

作用:對Git暫存文件進行pretty的操作。

  1. install:husky
package.json
1
2
3
"scripts": {
    "install:husky": "is-ci || husky install"
}

作用:如果不是CI服務器,就啟動Git鉤子。

  1. postinstall
package.json
1
2
3
"scripts": {
    "postinstall": "npm run install:husky"
}

作用:執行install:husky命令。這個命令會自動啟動Git鉤子,參考文檔

給husky添加任務

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 啟動husky
yarn install:husky

# 添加commit消息檢查任務到`.husky/commit-msg`文件中
# 這裡我一直執行不出來,是引號引其的問題,大家可以先創建空的然後把命令複製進去
# npx husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"'
npx husky add .husky/commit-msg

# 創建一個任務文件
npx husky add .husky/pre-commit

手動修改.husky/commit-msg文件:
這裡面的$1一定要用雙引號,不然後面執行會報錯。

.husky/commit-msg
1
2
3
4
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

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

手動修改.husky/pre-commit文件:

參考連結:

最後手動執行之前創建的腳本命令。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
. "$(dirname "$0")/common.sh"

[ -n "$CI" ] && exit 0

# Check the file name
# ! ls-lint cannot be used normally in mac pro of M1 system.
npm run lint:ls-lint

# Format and submit code according to lintstagedrc.js configuration
npm run lint:lint-staged

npm run lint:pretty

測試

失敗測試

最好先重啟你的ide

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
git add .
git commit -m "隨便打一些消息"

> [email protected] lint:ls-lint F:\CodeLearn\vben\vben3
> ls-lint

> [email protected] lint:lint-staged F:\CodeLearn\vben\vben3
> lint-staged -c ./.husky/lintstagedrc.js

[STARTED] Preparing...
# 一系列檢查...
[SUCCESS] Cleaning up...

> [email protected] lint:pretty F:\CodeLearn\vben\vben3
> pretty-quick --staged

�🔍  Finding changed files since git revision 1346762.
�🎯  Found 2 changed files.
✅  Everything is awesome!
⧗   input: 隨便打一些消息
✖   subject may not be empty [subject-empty]
type may not be empty [type-empty]

✖   found 2 problems, 0 warnings
ⓘ   Get help: https://github.com/conventional-changelog/commitlint/#what-is-commitlint

husky - commit-msg hook exited with code 1 (error)

格式

出錯了,我們規範的提交消息試試:

@commitlint/config-conventional的規範+我們自定義的規則。

type: some message

類型英文類型中文
build建立
chore雜事
cici
docs文檔
feat壯舉
fix修復
perf敷衍
refactor重構
revert恢復
style樣式
test測試

成功測試

提交成功,當然你不用擔心這樣手敲太麻煩了,後面還有插件可以自動幫你敲這些格式。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
git commit -m "feat: 我們成功的規範了Git提交!"

> [email protected] lint:pretty F:\CodeLearn\vben\vben3
> pretty-quick --staged

 �🔍  Finding changed files since git revision 1346762.
 �🎯  Found 2 changed files.
✅  Everything is awesome!
[master 6c61fc5] feat: 我們成功的規範了Git提交!
 7 files changed, 104 insertions(+), 4 deletions(-)
 create mode 100644 .husky/.gitignore
 create mode 100644 .husky/commit-msg
 create mode 100644 .husky/common.sh
 create mode 100644 .husky/lintstagedrc.js
 create mode 100644 .husky/pre-commit
 
# 推送出去
git push -u origin master

使用conventional-changelog-cli生成變更記錄

說明

VbenAdmin項目的腳本命令和根目錄中還有CHANGELOG.md這麼個文件,這是記錄我們項目變更記錄的MD。明顯不是手寫出來的,那麼怎麼生成?

參考連結:

安裝

1
yarn add global conventional-changelog-cli --dev

配置腳本

package.json文件中的scripts字段。

package.json
1
2
3
4
5
"scripts": {
    // ...
    "log": "conventional-changelog -p angular -i CHANGELOG.md -s",
    // ...
}

測試

1
yarn log

可以看見生成了CHANGELOG.md文件

1
2
3
4
5
6

# 0.0.0 (2022-10-13)

### Features

- 我们成功的规范了 Git 提交! ([6c61fc5](https://gitee.com/kuxiaoxin/vben3/commits/6c61fc5c0bc9d0ed551117eb00349ce82e35deea))

命令列參數

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
> conventional-changelog --help

Generate a changelog from git metadata

  Usage
    conventional-changelog

  Example
    conventional-changelog -i CHANGELOG.md --same-file

  Options
    -i, --infile              Read the CHANGELOG from this file
                              # 從這個文件中讀取CHANGELOG。

    -o, --outfile             Write the CHANGELOG to this file
                              If unspecified, it prints to stdout
                              # 將CHANGELOG寫入這個文件。
                              # 如果沒有指定,則打印到stdout。

    -s, --same-file           Outputting to the infile so you don\'t need to specify the same file as outfile
                              # 輸出到infile,所以你不需要指定與outfile相同的文件。

    -p, --preset              Name of the preset you want to use. Must be one of the following:
                              angular, atom, codemirror, ember, eslint, express, jquery, jscs or jshint
                              # 您要使用的預設名稱。必須是下列之一。
                              # angular, atom, codemirror,
                              # ember, eslint, express,
                              # jquery, jscs, jshint。

    -k, --pkg                 A filepath of where your package.json is located
                              Default is the closest package.json from cwd
                              # 您的 package.json 所在的文件路徑。
                              # 默認為cwd中最接近的package.json。

    -a, --append              Should the newer release be appended to the older release
                              Default: \false
                              # 是否應將較新的版本附加到較舊的版本上?
                              # 默認:不追加

    -r, --release-count       How many releases to be generated from the latest
                              If 0, the whole changelog will be regenerated and the outfile will be overwritten
                              Default: 1
                              # 將從最新的版本中生成多少個版本。
                              # 如果為0,整個變更日誌將被重新生成,並且輸出文件將被覆蓋。
                              # 默認值: 1

    --skip-unstable           If given, unstable tags will be skipped, e.g., x.x.x-alpha.1, x.x.x-rc.2
                              # 如果給定,將跳過不穩定的標籤,例如,x.x.x-alpha.1, x.x.x-rc.2。

    -u, --output-unreleased   Output unreleased changelog
                              # 輸出未發布的變更日誌。

    -v, --verbose             Verbose output. Use this \for debugging
                              Default: \false
                              # Verbose 輸出。用來調試
                              # 默認:不輸出

    -n, --config              A filepath of your config script
                              Example of a config script: https://github.com/conventional-changelog/conventional-changelog/blob/master/packages/conventional-changelog-cli/test/fixtures/config.js
                              # 配置腳本的文件路徑。
                              # 配置腳本的例子:
                              # https://github.com/conventional-changelog
                              # /conventional-changelog/blob/master/packages
                              # /conventional-changelog-cli/test/fixtures/config.js

    -c, --context             A filepath of a json that is used to define template variables
                              # 一個用於定義模板變量的json文件路徑。
    -l, --lerna-package       Generate a changelog \for a specific lerna package (:[email protected])
                              # 為一個特定的lerna包生成一個變更日誌(:[email protected])
    -t, --tag-prefix          Tag prefix to consider when reading the tags
                              # 讀取標籤時要考慮的標籤前綴。
    --commit-path             Generate a changelog scoped to a specific directory
                              # 生成一個特定目錄下的變更日誌。

配置Vite

說明

本章只是準備,大家可以直接把VbenAdmin的vite.config.ts原代碼複製到我們的項目中。然後把報錯的地方註釋掉。接下來的章節會把這個文件一段一段的解析出來。章節做太長了大家應該也不喜歡看,至少我不喜歡看長文章。

代碼

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
import type { UserConfig, ConfigEnv } from 'vite';

import { loadEnv } from 'vite';
import { resolve } from 'path';

// import { generateModifyVars } from './build/config/themeConfig';
// import { createProxy } from './build/vite/proxy';
// import { wrapperEnv } from './build/utils';
// import { createVitePlugins } from './build/vite/plugin';
// import { OUTPUT_DIR } from './build/constant';

function pathResolve(dir: string) {
  return resolve(__dirname, '.', dir);
}

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

  const env = loadEnv(mode, root)

  // The boolean type read by loadEnv is a string. This function can be converted to boolean type
  // const viteEnv = wrapperEnv(env);

  // const { VITE_PORT, VITE_PUBLIC_PATH, VITE_PROXY, VITE_DROP_CONSOLE, VITE_LEGACY } = viteEnv;

  // const isBuild = command === 'build';

  return {
    // base: VITE_PUBLIC_PATH,
    root,
    resolve: {
      alias: [
        {
          // /@/xxxx  =>  src/xxx
          find: /^\/@\//,
          replacement: pathResolve('src') + '/'
        },
        {
          // /#/xxxx  =>  types/xxx
          find: /^\/#\//,
          replacement: pathResolve('types') + '/'
        }
      ]
    },
    server: {
      // port: VITE_PORT,
      // Load proxy configuration from .env
      // proxy: createProxy(VITE_PROXY),
      hmr: {
        overlay: true
      }
    },

    build: {
      // minify: 'esbuild',
      // outDir: OUTPUT_DIR,
      // polyfillDynamicImport: VITE_LEGACY,
      terserOptions: {
        compress: {
          keep_infinity: true,
          // Used to delete console in production environment
          // drop_console: VITE_DROP_CONSOLE,
        },
      },
      // Turning off brotliSize display can slightly reduce packaging time
      brotliSize: false,
      chunkSizeWarningLimit: 1200
    },
    define: {
      // setting vue-i18-next
      // Suppress warning
      __VUE_I18N_LEGACY_API__: false,
      __VUE_I18N_FULL_INSTALL__: false,
      __INTLIFY_PROD_DEVTOOLS__: false
    },
    css: {
      preprocessorOptions: {
        less: {
          modifyVars: {
            // Used for global import to avoid the need to import each style file separately
            // reference:  Avoid repeated references
            hack: `true; @import (reference) "${resolve('src/design/config.less')}";`
            // ...generateModifyVars(),
          },
          javascriptEnabled: true
        }
      }
    },

    // The vite plugin used by the project. The quantity is large, so it is separately extracted and managed
    // plugins: createVitePlugins(viteEnv, isBuild),

    optimizeDeps: {
      // @iconify/iconify: The dependency is dynamically and virtually loaded by @purge-icons/generated, so it needs to be specified explicitly
      include: [
        '@iconify/iconify',
        'ant-design-vue/es/locale/zh_CN',
        'moment/dist/locale/zh-cn',
        'ant-design-vue/es/locale/en_US',
        'moment/dist/locale/eu',
      ],
      exclude: ['vue-demi']
    }
  }
}

Node的path.resolve方法

說明

psth.resolve([...paths])方法是NodeApipath模塊的方法。

參考連結

VbenAdmin原代碼

1
2
3
4
5
import { resolve } from 'path'

function pathResolve(dir: string) {
    return resolve(__dirname, '.', dir)
}

代碼在項目中報錯

此時引入項目會報錯:找不到 path
因為我們沒有安裝node的聲明文件,ts裡面沒有。

1
yarn add @types/node --dev

這個開發依賴在原VbenAdmin項目中沒有,是因為其他依賴中有這個依賴。
但是我們現在還沒有安裝包含@types/node的依賴。
剩下的依賴裡面有很多這樣的依賴,我們也不去猜是哪個依賴了。
反正是一個開發依賴,我們直接自己手動安裝了。

resolve方法

該方法百度上有兩種說法:

  1. 從後向前,生成絕對路徑。
  • 若字符以 / 開頭,不會拼接到前面的路徑(因為拼接到此已經是一個絕對路徑)。
  • 若以 ../ 開頭,拼接前面的路徑,且不含最後一節路徑。
  • 若以 ./ 開頭 或者沒有符號 則拼接前面路徑。
  • 需要注意的是:如果在處理完所有給定的 path 片段之後還未生成絕對路徑,則再加上當前工作目錄。
  1. 每一個參數都理解為一個cd命令。最終cd到哪個路徑。返回該絕對路徑。

__dirname

NodeApi__dirname:當前模塊目錄名。

在線運行

1
2
3
4
5
6
const path = require('path')
console.log(__dirname)
console.log(path.resolve(__dirname, '.', 'src'))

// /tmp/B3YQQ1
// /tmp/B3YQQ1/src

Vite的情景配置

說明

本章收集了一些Vite如何根據不同環境進行不同配置的資料。

參考連結

代碼

總之不管使用什麼方式配置Vite,最後返回的就只是一個對象。對像中包含Vite配置的細節。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
export default ({ command, mode }) => {
    if (command === "serve") {
        return {
        // serve 獨有配置
        }
    } else {
        return {
        // build 獨有配置
        }
    }
}

參數說明

vite目前只有vitevite buildvite preview這三種命令。以下是這三種命令的commandmode的值。

  • vite
    • command:serve
    • mode:development
  • vite build
    • command:build
    • mode:production
  • vite preview
    • command:serve
    • mode:development

Node的process

說明

返回 Node.js 進程的當前工作目錄。

參考連結

代碼

1
process.cwd()

Vite的loadEnv方法

說明

  • 檢查process.cwd()路徑下.env.development.local.env.development.env.local.env這四個環境文件。
  • 輸出NODE_ENVVITE_開頭的鍵值對。
  • VITE_開頭的鍵值對後面的不會覆蓋前面的。
  • NODE_ENV的值後面的會覆蓋前面的。

參考連結

原始碼

前置代碼

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// ↓ 加載node的path模塊
var path$1 = require('path');
var fs$2 = require('fs');

// ↓ 傳入的如果是一個包含'default'的key的對象,則輸出對象的'default'的key對應的value。
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e['default'] : e; }

// ↓ 結合上面二者
var path__default = /*#__PURE__*/_interopDefaultLegacy(path$1);
var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs$2);

// ↓ 檢查dir下是否有formats中的路徑,返回有就返迴路徑或者文件
function lookupFile(dir, formats, pathOnly = false) {
    for (const format of formats) {
        // ↓ 輸出dir和formats連接後的路徑
        const fullPath = path__default.join(dir, format);

        // ↓ 同步的檢查該路徑是否存在,並且該路徑對應的是一個文件
        if (fs__default.existsSync(fullPath) && fs__default.statSync(fullPath).isFile()) {

            // ↓ 是否只要輸出路徑,否則輸出文件
            return pathOnly ? fullPath : fs__default.readFileSync(fullPath, 'utf-8');
        }
    }

    // ↓ 上面的路徑都不滿足輸出條件,那麼再檢查一遍檢查傳入的dir的目錄名
    const parentDir = path__default.dirname(dir);
    if (parentDir !== dir) {
        return lookupFile(parentDir, formats, pathOnly);
    }
}

代碼

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// 從傳參的root目錄下獲取
// 按順序 .env.${mode}.local、.env.${mode}、.env.local、.env這四個環境文件
// 輸出文件內配置的對象
function loadEnv(mode, root, prefix = 'VITE_') {
    if (mode === 'local') {
        // ↓ 如果第一個參數傳入'local',就報錯:
        // ↓ "local "不能用作模式名稱,因為它與``.env文件的.local後綴衝突。
        throw new Error(`"local" cannot be used as a mode name because it conflicts with ` +
            `the .local postfix for .env files.`);
    }
    
    // ↓ 待輸出的環境變量對象
    const env = {};

    // ↓ 要讀取的四個文件名稱的字符串數組
    const envFiles = [
        /** mode local file */ `.env.${mode}.local`,
        /** mode file */ `.env.${mode}`,
        /** local file */ `.env.local`,
        /** default file */ `.env`
    ];
    
    // ↓ 檢查是否有實際的以VITE_*開頭的環境變量。
    // ↓ 這些通常是Node內聯提供的env對象,並應優先考慮。
    for (const key in process.env) {
        if (key.startsWith(prefix) && env[key] === undefined) {
            env[key] = process.env[key];
        }
    }
    
    
    for (const file of envFiles) {
        // ↓ 檢查根目錄下是否有指定配置文件
        const path = lookupFile(root, [file], true);
        if (path) {

            // ↓ 以換行為單位輸出文件中KEY=VAL格式的到結果對像中
            const parsed = main$2.parse(fs__default.readFileSync(path), {
                debug: !!process.env.DEBUG || undefined
            });

            // ↓ 讓環境變量互相使用,這個方法我沒仔細研究。不是很懂
            main$1({
                parsed,

                // ↓ 防止process.env修改
                ignoreProcessEnv: true
            });

            // ↓ 只輸出以prefix開頭的key
            for (const [key, value] of Object.entries(parsed)) {
                // ↓ 只有這個key在前面沒有加載過才賦值
                if (key.startsWith(prefix) && env[key] === undefined) {
                    env[key] = value;
                }
                else if (key === 'NODE_ENV') {
                    // ↓ 在.env文件中覆蓋NODE_ENV。
                    process.env.VITE_USER_NODE_ENV = value;
                }
            }
        }
    }
    return env;
}

Vben中的wrapperEnv方法

說明

1
2
// The boolean type read by loadEnv is a string. This function can be converted to boolean type
const viteEnv = wrapperEnv(env);

翻譯如下:

loadEnv讀取的布爾類型是一個字符串。該函數可以轉換為布爾類型。

創建wrapperEnv方法

從這開始涉及到根目錄下的build文件夾了。創建該文件夾。我們使用build文件夾裝所有關於項目構建類的東西。

創建定義文件

根目錄下的build文件夾創建typeing.d.ts文件。

typeing.d.ts
1
2
3
4
5
6
declare module '*.json' {
    const src: any;
    export default src;
}

declare type Recordable = Record<string, any>;

參考連結

定義Env對像類型

根目錄下的build文件夾創建utils.ts文件。

utils.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
export interface ViteEnv {
    VITE_PORT: number
    VITE_USE_MOCK: boolean
    VITE_USE_PWA: boolean
    VITE_PUBLIC_PATH: string
    VITE_PROXY: [string, string][]
    VITE_GLOB_APP_TITLE: string
    VITE_GLOB_APP_SHORT_NAME: string
    VITE_USE_CDN: boolean
    VITE_DROP_CONSOLE: boolean
    VITE_BUILD_COMPRESS: 'gzip' | 'brotli' | 'none'
    VITE_LEGACY: boolean
    VITE_USE_IMAGEMIN: boolean
}

導出的這個類型對應了我們配置文件中的Key。

格式化Env對象的wrapperEnv方法

utils.ts文件中添加以下方法:

utils.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
export function wrapperEnv(envConf: Recordable): ViteEnv {
  const ret: any = {};

  for (const envName of Object.keys(envConf)) {
    let realName = envConf[envName].replace(/\\n/g, '\n');
    realName = realName === 'true' ? true : realName === 'false' ? false : realName;

    if (envName === 'VITE_PORT') {
      realName = Number(realName);
    }
    if (envName === 'VITE_PROXY') {
      try {
        realName = JSON.parse(realName);
      } catch (error) {}
    }
    ret[envName] = realName;
    process.env[envName] = realName;
  }
  return ret;
}
代碼說明:
  • 中間的正則替換就是將\\n替換成\n,複製下面這些代碼可以去瀏覽器中體驗一下。
    • console.log('asda\\\n\\n\n'.replace(/\n/g, '\n')) => asda\ [回車\n] \n [回車\n]
    • console.log('asda\\\n\\n\n'.replace(/\\n/g, '\n')) => asda\ [回車\n] [回車\n] [回車\n]
    • console.log('asda\\\n\\n\n'.replace(/\\\n/g, '\n')) => asda [回車\n] \n [回車\n]

vite.config.ts文件復盤

自此vite.config.ts,除return裡面的東西外,其他的都已經解析完了。以現在可以得到這樣一個代碼:

vite.config.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import { resolve } from 'path';
import { ConfigEnv, loadEnv, UserConfig } from 'vite';
import { wrapperEnv } from './build/util';

function pathResolve(dir: string) {
  return resolve(__dirname, '.', dir);
}

export default ({ command, mode }: ConfigEnv): UserConfig => {
  const root = process.cwd();
  const env = loadEnv(mode, root);
  const viteEnv = wrapperEnv(env);
  const { VITE_PORT, VITE_PUBLIC_PATH, VITE_PROXY, VITE_DROP_CONSOLE, VITE_LEGACY } = viteEnv;
  const isBuild = command === 'build';
  return {};
};

Vite配置-base

說明

Vite配置-base說明

官網原文:

  • 類型: string
  • 默認: /

開發或生產環境服務的 公共基礎路徑。合法的值包括以下幾種:

  • 絕對 URL 路徑名,例如 /foo/
  • 完整的 URL,例如 https://foo.com/
  • 空字符串或 ./(用於開發環境)

更多信息詳見公共基礎路徑

就是你的項目的BaseURL。比如這裡我配置一個yiu
那麼我的項目訪問:http://localhost:3000/yiu/
當你訪問其他路徑就會報錯:http://localhost:3000/any/

1
The server is configured with a public base URL of /yiu/ - did you mean to visit /yiu/any/ instead?

Vite配置-root

說明

Vite配置-root說明

官網原文:

  • 類型: string
  • 默認: process.cwd()

項目根目錄(index.html 文件所在的位置)。可以是一個絕對路徑,或者一個相對於該配置文件本身的路徑。

更多信息詳見項目根目錄

用來找index.html路徑的配置吧,亂寫啟動項目的時候會報錯。其他的也不用解釋了。


Vite配置-resolve.alias

說明

Vite配置-resolve.alias說明

官網原文:

  • 類型:
    1
    
    Record<string, string> | Array<{ find: string | RegExp, replacement: string }>
    

將會被傳遞到 @rollup/plugin-alias 作為它的 entries。也可以是一個對象,或一個 { find, replacement } 的數組。
當使用文件系統路徑的別名時,請始終使用絕對路徑。相對路徑作別名值將按原樣使用導致不會解析到文件系統路徑中。
更高級的自定義解析方法可以通過 插件 實現。

VbenAdmin的示例:

vite.config.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
resolve: {
    alias: [
        {
            // /@/xxxx  =>  src/xxx
            find: /^\/@\//,
            replacement: pathResolve('src') + '/',
        },
        {
            // /#/xxxx  =>  types/xxx
            find: /^\/#\//,
            replacement: pathResolve('types') + '/',
        },
    ],
},

Vite配置-server.port

說明

Vite配置-server.port說明

官網原文:

  • 類型: number

指定服務器端口。注意:如果端口已經被使用,Vite 會自動嘗試下一個可用的端口,所以這可能不是服務器最終監聽的實際端口。


Vite配置-server.proxy

說明

官網原文:

  • 類型:
    1
    
    Record<string, string | ProxyOptions>
    

為開發服務器配置自定義代理規則。期望接收一個 { key: options } 對象。如果 key 值以 ^ 開頭,將會被解釋為 RegExp
使用 http-proxy。完整選項詳見 此處

示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
export default {
  server: {
    proxy: {
      // 字符串簡寫寫法
      '/foo': 'http://localhost:4567/foo',
      // 選項寫法
      '/api': {
        target: 'http://jsonplaceholder.typicode.com',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      },
      // 正則表達式寫法
      '^/fallback/.*': {
        target: 'http://jsonplaceholder.typicode.com',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/fallback/, '')
      }
    }
  }
}

VbenAdmin代碼解析

我們在build文件夾下創建一個vite文件,這麼放置所有和vite配置有關的文件。然後完成本章兩個文件的編寫。

proxy.ts

build\vite\proxy.ts,注意,這裡帶註解的代碼和原代碼不一樣。因為原代碼我感覺有點問題。
Vite官方文檔說代理配置接收的是一個Record<string, string | ProxyOptions>對象。而這個文件的主要方法返回的是ProxyTargetList類型的對象。

按理來說ProxyTargetList應該直接等於Record<string, string | ProxyOptions>

但是VbenAmin的代碼安裝了一個@types/http-proxy(雖然Vite本身的代理就是用http-proxy實現的)的開發依賴。然後從這個開發依賴裡面導入一個ServerOptions。讓ProxyTargetList等於Record<string, ServerOptions & { rewrite: (path: string) => string }>

個人感覺可以直接使用vite提供的類型即可,不用安裝@types/http-proxy的聲明文件。

代碼

被代理之後,訪問的路徑是 代理至的路徑 + 訪問的路徑去掉根路徑後經過rewrite處理的路徑。

build/vite/proxy.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
 * .env.development 的 代理配置
 */
import type { ProxyOptions } from 'vite';

// ↓ 第一项被代理的路径,第二项代理至的路径
type ProxyItem = [string, string];


// ↓ 方法接收的参数
type ProxyList = ProxyItem[];


// ↓ Vite代理所接收对象类型
type ProxyTargetList = Record<string, string | ProxyOptions>;


// ↓ https类型的URL的匹配正则
const httpsRE = /^https:\/\//;

/**
 * 生成Vite代理配置的方法
 */
export function createProxy(list: ProxyList = []) {
  const ret: ProxyTargetList = {};
  for (const [prefix, target] of list) {
    const isHttps = httpsRE.test(target);

    // https://github.com/http-party/node-http-proxy#options
    ret[prefix] = {
      // ↓ 代理至的路径
      target: target,
      
      // ↓ 默认值:false-将主机标头的来源更改为目标URL
      changeOrigin: true,
      
      // ↓ 如果您想代理websocket
      ws: true,
      rewrite: (path) => path.replace(new RegExp(`^${prefix}`), ''),

      // https is require secure=false
      ...(isHttps ? { secure: false } : {}),
    };
  }
  return ret;
}
vite.config.ts

直接將上面定義的生成代理配置的方法放到vite.config.ts中使用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// ...
import { createProxy } from './build/vite/proxy';

// ...
export default ({ command, mode }: ConfigEnv): UserConfig => {
  // ...
  return {
    // ...
    server: {
   	  // ...
      // 從.env加載代理配置
      proxy: createProxy(VITE_PROXY),
    },
    // ...
  };
};

Vite配置-server.hmr

說明

Vite配置-server.hmr說明

官網原文:

  • 類型:
    1
    
    boolean | { protocol?: string, host?: string, port?: number, path?: string, timeout?: number, overlay?: boolean }
    

禁用或配置 HMR 連接(用於 HMR websocket 必須使用不同的 http 服務器地址的情況)。

設置 server.hmr.overlayfalse 可以禁用服務器錯誤遮罩層。

這個就是配置Vite的熱更新的。文檔中說的服務器錯誤遮罩層,就是你在代碼中編寫編寫錯誤的代碼,編譯不通過的時候,瀏覽器頁面也會同時展示一個灰屏上面顯示你的代碼錯誤。


Vite配置-build.minify

說明

Vite配置-build.minify說明

官網原文:

  • 類型:
    1
    
    boolean | 'terser' | 'esbuild'
    
  • 默認:terser

設置為 false 可以禁用最小化混淆,或是用來指定使用哪種混淆器。默認為 Terser,雖然 Terser 相對較慢,但大多數情況下構建後的文件體積更小。 ESbuild 最小化混淆更快但構建後的文件相對更大。


Vite配置-build.outDir

說明

Vite配置-build.outDir說明

官網原文:

  • 類型:string
  • 默認:dist

指定輸出路徑(相對於 項目根目錄)。

創建全局常數

這個輸出路徑我們把它寫成一個全局常量。全局常量保存在:build\constant.ts

build\constant.ts
1
2
3
4
5
6
/**
 * 在生产环境中输入的配置文件名称。
 */
export const GLOB_CONFIG_FILE_NAME = '_app.config.js';

export const OUTPUT_DIR = 'dist';

然後在vite.config.ts中引用:

vite.config.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// ...
import { OUTPUT_DIR } from './build/constant';

// ...
export default ({ command, mode }: ConfigEnv): UserConfig => {
  // ...
  return {
    // ...
    build: {
      outDir: OUTPUT_DIR,
    },
    // ...
  };
};

Vite配置-build.polyfillDynamicImport

說明

官網原文:

  • 類型:boolean
  • 默認:true,除非build.targetesnext

是否自動注入動態導入polyfill

polyfill會自動注入到每個index.html條目的代理模塊中。如果通過build.rollupOptions.input配置成使用非html的自定義條目,那麼就需要手動導入自定義條目中的polyfill

1
import 'vite/dynamic-import-polyfill'

注意:polyfill不適用於庫模式。如果你需要支持沒有本地動態導入的瀏覽器,你可能應該避免在你的庫中使用它。

Vite使用ES動態導入作為代碼分割點。生成的代碼還將使用動態導入來加載異步塊。然而,本機ESM動態導入支持是在ESM之後通過腳本標記實現的,並且這兩個特性在瀏覽器支持方面存在差異。 Vite會自動注入一個輕量級的動態導入填充來消除這種差異。

如果你知道你的目標瀏覽器只支持本機動態導入,你可以通過build.polyfillDynamicImport顯式禁用此特性。

總之VbenAdmin中在生產環境將此項禁用了。如果打包後的項目有問題,而本地運行沒問題。那麼嘗試打開它,因為這個配置貌似和瀏覽器有關。

創建全局常數

這個輸出路徑我們把它寫成一個全局常量。全局常量保存在:build\constant.ts

build\constant.ts
1
2
3
4
5
6
/**
 * 在生产环境中输入的配置文件名称。
 */
export const GLOB_CONFIG_FILE_NAME = '_app.config.js';

export const OUTPUT_DIR = 'dist';

然後在vite.config.ts中引用:

vite.config.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// ...
import { OUTPUT_DIR } from './build/constant';

// ...
export default ({ command, mode }: ConfigEnv): UserConfig => {
  // ...
  return {
    // ...
    build: {
      outDir: OUTPUT_DIR,
    },
    // ...
  };
};

Vite配置-build.terserOptions

說明

Vite配置-build.terserOptions說明

官網原文:

  • 類型:TerserOptions

傳遞給 Terser 的更多 minify 選項

其他的配置項自行研究,這裡只註釋一下VbenAdmin配置的代碼。

vite.config.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// ...
export default ({ command, mode }: ConfigEnv): UserConfig => {
  // ...
  return {
    // ...
    build: {
      // ...
      terserOptions: {
        compress: {
          keep_infinity: true,
          // 用於刪除生產環境中的console
          drop_console: VITE_DROP_CONSOLE,
        },
      },
    },
    // ...
  };
};
  • compress:(默認{})-通過false以完全跳過壓縮。傳遞一個對像以指定自定義壓縮選項
  • compress.keep_infinity:(默認值:false)-通過true以防止Infinity被壓縮為1/0,這可能會導致Chrome出現性能問題。
  • compress.drop_console:(默認值:false)-傳遞true以放棄對console.*函數的調用 。如果您希望在刪除函數調用後刪除特定的函數調用,例如console.info和/或保留函數自變量的副作用,請pure_funcs改用。

Vite配置-build.brotliSize

說明

Vite配置-build.brotliSize說明

官網原文:

  • 類型:boolean
  • 默認:true

啟用/禁用 brotli 壓縮大小報告。壓縮大型輸出文件可能會很慢,因此禁用該功能可能會提高大型項目的構建性能。

禁用就好。

效果對比

打開壓縮報告:4.90s

關閉壓縮報告:3.50s,少了後面一串壓縮大小,肯定更快啦。


Vite配置-build.chunkSizeWarningLimit

說明

Vite配置-build.chunkSizeWarningLimit說明

官網原文:

  • 類型:number
  • 默認:500

chunk 大小警告的限制(以 kbs 為單位)。

LimitChunkCountPlugin

在編寫代碼時,您可能已經添加了許多代碼拆分點以按需加載內容。編譯後,您可能會注意到一些塊太小了-造成更大的HTTP開銷。 LimitChunkCountPlugin可以通過合併來對您的塊進行後處理。


Vite配置-define

說明

Vite配置-define說明

官網原文:

  • 類型:
    1
    
    Record<string, string>
    

定義全局變量替換方式。每項在開發時會被定義為全局變量,而在構建時則是靜態替換。

  • 2.0.0-beta.70 版本開始,字符串值將作為一個直接的表達式,所以如果定義為了一個字符串常量,它需要被顯式地引用(例如:通過 JSON.stringify)。
  • 替換只會在匹配到周圍是單詞邊界(\b)時執行。

使用

看一下vue-i18n的應用。

vue-i18n的全局變量替換

原文:

Bundler 構建功能標誌

esm-bundler builds現在暴露了全局特性標誌,這些標誌可以在編譯時被覆蓋。

  • __VUE_I18N_FULL_INSTALL__:啟用/禁用,除了vue-i18n APIs,組件和指令都完全支持安裝:true

  • __VUE_I18N_LEGACY_API__:啟用/禁用vue-i18n傳統風格的API支持,默認為true

  • __INTLIFY_PROD_DEVTOOLS__:在生產中啟用/禁用intlify-devtoolsvue-devtools支持,默認為false

在沒有配置這些標誌的情況下,編譯也能正常工作,但強烈建議正確配置這些標誌,以便在最終的bundle中獲得正確的樹形搖動。

要配置這些標誌:

  • webpack: 使用 DefinePlugin
  • Rollup: 使用 @rollup/plugin-replace
  • Vite:默認配置,但可以使用define選項覆蓋

注意:替換值必須是布爾文,不能是字符串,否則bundler/minifier將無法正確評估條件。

如此,就按照VbenAdmin的原代碼一樣配置吧。不然vue-i18n還會包警告,當然,現在我們還沒有安裝vue-i18n。後面配置國際化的時候再講。


Vite配置-css.preprocessorOptions

說明

Vite配置-css.preprocessorOptions說明

官網原文:

  • 類型:
    1
    
    Record<string, object>
    

指定傳遞給 CSS 預處理器的選項。例如:

vite.config.ts
1
2
3
4
5
6
7
8
9
export default {
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `$injectedColor: orange;`
      }
    }
  }
}

安裝less

眾所周知Antd框架使用的是less。所以我們這裡研究less的配置。

安裝less:

1
yarn add less --dev

VbenAdmin的全局樣式

在根目錄下的:src\design文件夾中定義了VbenAdmin所有 全局樣式 和 全局less變量 。其中分為兩大類:

  • src\design\index.less:全局樣式,在main.ts中使用。
  • src\design\config.less:全局變量,在vite.config.ts中使用。

至於該文件夾下的其他文件,都被直接或間接的引入到了index.lessconfig.less中。

我們這邊直接拷貝文件夾就行了,樣式也沒啥研究的。如果有能力的可以把和index.less相關的文件都刪除了,然後自己寫關於VbenAdmin的樣式。
但是變量不能動,很多都antd的變量,antd 的 less 變量參考連結
複製完之後,順便將index.less在main.ts中引用一下。

Vite中的Less配置

代碼配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// ...
export default ({ command, mode }: ConfigEnv): UserConfig => {
  // ...
  return {
    // ...
    css: {
      preprocessorOptions: {
        less: {
          modifyVars: {
            // 用於全局導入,以避免需要單獨導入每個樣式文件。
            // reference:  避免重複引用
            hack: `true; @import (reference) "${resolve('src/design/config.less')}";`,
            // ↓ 這行代碼下一章講
            // ...generateModifyVars(),
          },
          javascriptEnabled: true,
        },
      },
    },
    // ...
  };
};

代碼解析

less對應的對象可以查看lessjs-配置項

  • javascriptEnabled: true這行代碼沒得說,畢竟antdless文件中使用JS寫法,比如:\node_modules\ant-design-vue\lib\style\color\bezierEasing.less
  • modifyVars是在全局less文件後面添加變量的配置。 modifyVars對應的對象屬性名會加上@追加到less文件後。
  • hack: true; @import (r...:這一行代碼,在less文件中會被解析成:
1
@hack=true; @import (reference) "${resolve('src/design/config.less')}";

那這個hack變量是幹什麼的?我感覺啥也沒有,單純是為了將後面的@import接上的@import會報錯,只寫。這行代碼可以隨便寫,比如:

1
2
3
// yiu: `red; @import (reference) "${resolve('src/design/config.less')}";`,
// ↓ 解析成,一樣的效果。
@yiu=red; @import (reference) "${resolve('src/design/config.less')}";`,

我個人感覺這個hackcss hack單純名字一樣。至於reference,可以看一下關於Less js的@import實驗這篇博文即可理解。只是我個人疑惑為什麼這個import為什麼不寫在globalVars中。


Vite的顏色

說明

解析build\config\themeConfig.ts文件,直接創建這個文件。然後我們一段一段的分析:

1
yarn add ant-design-vue@next

第一部分3-16行代碼

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// ↓ 默認顏色
export const primaryColor = '#0960bd';

// ↓ 默認主題
export const themeMode = 'light';

// ↓ 主題類型
export type ThemeMode = 'dark' | 'light';

// ↓ 一個解決臨時檢查的方法類型
type Fn = (...arg: any) => any;

// ↓ Vite生成顏色組的方法參數,顏色這一塊有點深奧了,不做細的研究,感興趣的同學自己看看源碼吧
export interface GenerateColorsParams {
  mixLighten: Fn;
  mixDarken: Fn;
  tinycolor: any;
  color?: string;
}

第二部分18-22行代碼

1
2
3
4
5
6
7
import { generate } from '@ant-design/colors';

export function generateAntColors(color: string, mode: ThemeMode) {
  return generate(color, {
    theme: mode == 'dark' ? 'dark' : 'default',
  });
}

調用的@ant-design/colors的顏色生成方法,將參數簡化了一下。

參考連結:

第三部分24-32行代碼

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
export function getThemeColors(color?: string, theme?: ThemeMode) {
  const tc = color || primaryColor;
  const tm = theme || themeMode;

  // ↓ 傳入的顏色根據主題生成10個顏色系列
  const colors = generateAntColors(tc, tm);

  // ↓ 取10個顏色的第6個作為主顏色
  const primary = colors[5];

  // ↓ 再使用主顏色根據主題生成10個顏色系列
  const modeColors = generateAntColors(primary, tm === 'dark' ? 'light' : 'dark');

  // ↓ 輸出這20個顏色
  return [...colors, ...modeColors];
}

第四部分34-71行代碼

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
export function generateColors({
  color = primaryColor,
  mixLighten,
  mixDarken,
  tinycolor,
}: GenerateColorsParams) {
  const arr = new Array(19).fill(0);
  const lightens = arr.map((t, i) => {
    return mixLighten(color, i / 5);
  });

  const darkens = arr.map((t, i) => {
    return mixDarken(color, i / 5);
  });

  const alphaColors = arr.map((t, i) => {
    return tinycolor(color)
      .setAlpha(i / 20)
      .toRgbString();
  });

  const tinycolorLightens = arr
    .map((t, i) => {
      return tinycolor(color)
        .lighten(i * 5)
        .toHexString();
    })
    .filter((item) => item !== '#ffffff');

  const tinycolorDarkens = arr
    .map((t, i) => {
      return tinycolor(color)
        .darken(i * 5)
        .toHexString();
    })
    .filter((item) => item !== '#000000');
  return [...lightens, ...darkens, ...alphaColors, ...tinycolorDarkens, ...tinycolorLightens];
}

這裡直接根據顏色生成了98中顏色。我確實不太清楚這個麼多顏色幹什麼的。調用效果參考build\vite\plugin\theme.ts(現在先不管這個文件):

build/vite/plugin/theme.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
/**
 * Vite plugin for website theme color switching
 * https://github.com/anncwb/vite-plugin-theme
 */
import { viteThemePlugin, mixLighten, mixDarken, tinycolor } from 'vite-plugin-theme';
import { getThemeColors, generateColors } from '../../config/themeConfig';

export function configThemePlugin() {
  const colors = generateColors({
    mixDarken,
    mixLighten,
    tinycolor,
  });

  // ...
}

#0960bd生成的顏色:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
console.log([...lightens, ...darkens, ...alphaColors, ...tinycolorDarkens, ...tinycolorLightens]);
// ↓ 輸出
[
  '#0960bd',                '#3a80ca',                '#6ba0d7',
  '#9dbfe5',                '#cedff2',                '#ffffff',
  '#13011f10c',             '#16113f119',             '#19315e127',
  '#1c417e134',             '#1f519e141',             '#2261be14e',
  '#2571de15b',             '#2891fd169',             '#2ba21d176',
  '#2eb23d183',             '#31c25d190',             '#34d27d19d',
  '#37f29c1ab',             '#0960bd',                '#074d97',
  '#053a71',                '#04264c',                '#021326',
  '#000000',                '#-2-13-26',              '#-4-26-4c',
  '#-5-3a-71',              '#-7-4d-97',              '#-9-60-bd',
  '#-b-73-e3',              '#-d-86-109',             '#-e-9a-12e',
  '#-10-ad-154',            '#-12-c0-17a',            '#-14-d3-1a0',
  '#-16-e6-1c6',            '#-17-fa-1eb',            'rgba(9, 96, 189, 0)',
  'rgba(9, 96, 189, 0.05)', 'rgba(9, 96, 189, 0.1)',  'rgba(9, 96, 189, 0.15)',
  'rgba(9, 96, 189, 0.2)',  'rgba(9, 96, 189, 0.25)', 'rgba(9, 96, 189, 0.3)',
  'rgba(9, 96, 189, 0.35)', 'rgba(9, 96, 189, 0.4)',  'rgba(9, 96, 189, 0.45)',
  'rgba(9, 96, 189, 0.5)',  'rgba(9, 96, 189, 0.55)', 'rgba(9, 96, 189, 0.6)',
  'rgba(9, 96, 189, 0.65)', 'rgba(9, 96, 189, 0.7)',  'rgba(9, 96, 189, 0.75)',
  'rgba(9, 96, 189, 0.8)',  'rgba(9, 96, 189, 0.85)', 'rgba(9, 96, 189, 0.9)',
  '#0960bd',                '#0854a5',                '#07478c',
  '#063b74',                '#042f5c',                '#032243',
  '#02162b',                '#010913',                '#0960bd',
  '#0a6cd5',                '#0b79ee',                '#1e86f4',
  '#3793f5',                '#4fa0f7',                '#67adf8',
  '#80baf9',                '#98c7fa',                '#b0d4fb',
  '#c9e2fc',                '#e1effe',                '#f9fcff'
]


console.log(
    [...lightens, ...darkens, ...alphaColors, ...tinycolorDarkens, ...tinycolorLightens].length
  );
// ↓ 輸出
78

第五部分73-104行代碼

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
 * less global variable
 */
export function generateModifyVars() {
  const palettes = generateAntColors(primaryColor, themeMode);
  const primary = palettes[5];

  const primaryColorObj: Record<string, string> = {};

  for (let index = 0; index < 10; index++) {
    primaryColorObj[`primary-${index + 1}`] = palettes[index];
  }

  return {
    'primary-color': primary,
    ...primaryColorObj,
    'info-color': primary,
    'processing-color': primary,
    'success-color': '#55D187', //  Success color
    'error-color': '#ED6F6F', //  False color
    'warning-color': '#EFBD47', //   Warning color
    'disabled-color': 'rgba(0, 0, 0, 0.25)', //  Failure color
    'heading-color': 'rgba(0, 0, 0, 0.85)', //  Title color
    'text-color': 'rgba(0, 0, 0, 0.85)', //  Main text color
    'text-color-secondary': 'rgba(0, 0, 0, 0.45)', // Subtext color
    'font-size-base': '14px', //  Main font size
    'box-shadow-base': '0 2px 8px rgba(0, 0, 0, 0.15)', //  Floating shadow
    'border-color-base': '#d9d9d9', //  Border color,
    'border-radius-base': '2px', //  Component/float fillet
    'link-color': primary, //   Link color
  };
}

這個方法就是生成antd主題變量對象的。將該對象的結果放到Viteless配置中:

vite.config.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// ...
import { generateModifyVars } from './build/config/themeConfig';

// ...
export default ({ command, mode }: ConfigEnv): UserConfig => {
  // ...
  return {
    // ...
    css: {
      preprocessorOptions: {
        less: {
          modifyVars: {
            // ...
            ...generateModifyVars(),
          },
          // ...
        },
      },
    },
    // ...
  };
};

Vite配置-plugin

說明

Vite配置-plugin說明

官網原文:

  • 類型:
    1
    
    (Plugin | Plugin[])[]
    

將要用到的插件數組。查看 插件 API 獲取 Vite 插件的更多細節。

配置插件

它接收一個插件對象,或者一個插件數組。由於我們需要配置很多插件,而每一個插件的配置也不一樣,所以我們把插件數組封裝到一個方法中去。統一配置VbenAdmin的插件。

創建生成插件數組方法

build/vite/plugin/index.ts
1
2
3
4
5
6
7
8
9
import type { Plugin } from 'vite';
import type { ViteEnv } from '../../utils';

export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
  const { VITE_USE_IMAGEMIN, VITE_USE_MOCK, VITE_LEGACY, VITE_BUILD_COMPRESS } = viteEnv;

  const vitePlugins: (Plugin | Plugin[])[] = [];
  return vitePlugins;
}

後續配置vitePlugins這個數組。

配置Vite插件

vite.config.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// ...
import { createVitePlugins } from './build/vite/plugin';
// ...
export default ({ command, mode }: ConfigEnv): UserConfig => {
  // ...
  return {
    // ...
    // 項目使用的vite插件。數量大,單獨提取管理
    plugins: createVitePlugins(viteEnv, isBuild),

    // ...
  };
};

Vite插件-@vitejs-plugin-vue

說明

@vitejs/plugin-vue的npm首頁

要編寫Vue程序,這個不用解釋了吧,在使用Vite創建應用程序的時候,這個依賴就已經默認加上了。

引入組件

vue插件不用配置,可以直接放進去。

build/vite/plugin/index.ts
1
2
3
4
5
6
7
8
9
// ...
import vue from '@vitejs/plugin-vue';
// ...
export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
  // ...
  const vitePlugins: (Plugin | Plugin[])[] = [vue()];
  // ...
  return vitePlugins;
}

Vite插件-@vitejs-plugin-vue-jsx

說明

為什麼使用JSX

  • 其實JSX的效果和我們在vuetemplate中寫的代碼效果是一樣的。最終都會被渲染成createElement
  • 區別是template的標籤是不可變的,我們要實現動態標籤,只能使用v-if。而JSX的最大特點就是靈活,我們可以隨意組裝HTML代碼。

假如我們要實現一個組件渲染<hn></hn>標籤,n是我們傳入的參數。如果用template,那麼我們要寫6v-if。但是如果使用JSX,我們就可以直接將n放到標籤中去。

參考連結

安裝

1
yarn add @vitejs/plugin-vue-jsx --dev

vue-jsx插件不用配置,可以直接放進去。

build/vite/plugin/index.ts
1
2
3
4
5
6
7
8
9
// ...
import vueJsx from '@vitejs/plugin-vue-jsx';
// ...
export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
  // ...
  const vitePlugins: (Plugin | Plugin[])[] = [vue(),vueJsx()];
  // ...
  return vitePlugins;
}

Vite插件-@vitejs-plugin-legacy

說明

原文描述:
注:此插件需要vite@^2.0.0-beta.12
Vite默認的瀏覽器支持基線是原生ESM。本插件為不支持原生ESM的傳統瀏覽器提供支持。

默認情況下,該插件將為最終bundle中的每個chunk生成一個相應的legacy chunk,用@babel/reset-env進行轉換,並以SystemJS模塊的形式發布(仍然支持代碼分割!)。

生成一個包含SystemJS運行時的polyfill chunk,以及由指定的瀏覽器目標和捆綁包中的實際使用情況決定的任何必要的polyfills

在生成的HTML中註入<script nomodule>標籤,以便在沒有本地ESM支持的瀏覽器中有條件地加載polyfillslegacy bundle

注入 import.meta.env.LEGACY env 變量,該變量僅在 legacy 生產構建中為真,而在所有其他情況下為假。(需要vite@^2.0.0-beta.69)。

所以這個是一個瀏覽器兼容的插件。我們直接安裝使用,個人感覺學習的話也用不上。

參考連結

安裝

1
yarn add @vitejs/plugin-legacy --dev

VbenAdmin配置build/vite/plugin/index.ts

build/vite/plugin/index.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// ...
import legacy from '@vitejs/plugin-legacy';
// ...
export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
  // ...
  // @vitejs/plugin-legacy
  VITE_LEGACY && isBuild && vitePlugins.push(legacy());
  // ...
  return vitePlugins;
}

Vite插件-vite-plugin-html

說明

原文描述:

一個為index.html提供minify和基於EJS模板功能的Vite插件。

  • minify:壓縮index.html代碼。
  • EJS:給index.html提供訪問變量的能力。

參考連結

安裝

1
yarn add vite-plugin-html --dev

創建配置文件

build/vite/plugin/html.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
 * Plugin to minimize and use ejs template syntax in index.html.
 * https://github.com/anncwb/vite-plugin-html
 */
import type { Plugin } from 'vite';
import type { ViteEnv } from '../../utils';

import html from 'vite-plugin-html';

import pkg from '../../../package.json';
import { GLOB_CONFIG_FILE_NAME } from '../../constant';

export function configHtmlPlugin(env: ViteEnv, isBuild: boolean) {
  const { VITE_GLOB_APP_TITLE, VITE_PUBLIC_PATH } = env;

  // ↓ 這里後續章動態配置講解
  // const path = VITE_PUBLIC_PATH.endsWith('/') ? VITE_PUBLIC_PATH : `${VITE_PUBLIC_PATH}/`;
  // const getAppConfigSrc = () => {
  //   return `${path || '/'}${GLOB_CONFIG_FILE_NAME}?v=${pkg.version}-${new Date().getTime()}`;
  // };

  const htmlPlugin: Plugin[] = html({
    minify: isBuild,
    inject: {
      // Inject data into ejs template
      injectData: {
        title: VITE_GLOB_APP_TITLE,
      },
      // ↓ 這里後續章動態配置講解
      // tags: isBuild
      //   ? [
      //       {
      //         tag: 'script',
      //         attrs: {
      //           src: getAppConfigSrc(),
      //         },
      //       },
      //     ]
      //   : [],
    },
  });
  return htmlPlugin;
}

配置Vite插件

build/vite/plugin/index.ts
1
2
3
4
5
6
7
8
9
// ...
import { configHtmlPlugin } from './html';

export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
  // ...
  // vite-plugin-html
  vitePlugins.push(configHtmlPlugin(viteEnv, isBuild));
  // ...
}

補充

做完本章操作之後,我突然發現項目不能編譯了。
總是報package.json找不到。我看了一下代碼,主要就是build\vite\plugin\html.ts中的導入無法找到。
我反復對比了一下VbenAdmin的源碼。也沒有發現有什麼不同的地方。
然後我又試了一下,當執行vite命令的時候,build文件夾下始終不能訪問package.json。但是srcvite.config.ts中都可以導入訪問。

那麼這裡有兩種講解方案:

去掉導入

這裡的導入只是我們在使用_app.config.js的時候加上一個版本信息。可以讓我們知道這個項目是多久打包的,打包的時候版本是多少。所以去掉也無妨,_app.config.js還是一樣可以訪問的。

vite.config.ts中傳參下去

我使用了這個方案,因為萬一build文件夾其他地方要用package.json呢。不過用的地方應該也很少吧。

修改vite.config.ts

vite.config.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// ...
import pkg from './package.json';

// ...
export default ({ command, mode }: ConfigEnv): UserConfig => {
  // ...
  return {
   	// ...
    // The vite plugin used by the project. The quantity is large, so it is separately extracted and managed
    plugins: createVitePlugins(viteEnv, isBuild, pkg),
	// ...
  };
};

修改build/vite/plugin/index.ts

build/vite/plugin/index.ts
1
2
3
4
5
6
7
// ...
export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean, pkg: any) {
  // ...
  vitePlugins.push(configHtmlPlugin(viteEnv, isBuild, pkg));

  return vitePlugins;
}

修改build/vite/plugin/html.ts

build/vite/plugin/html.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// ...
import html from 'vite-plugin-html';

export function configHtmlPlugin(env: ViteEnv, isBuild: boolean, pkg: any) {
  // ...
  const getAppConfigSrc = () => {
    return `${path || '/'}${GLOB_CONFIG_FILE_NAME}?v=${pkg.version}-${new Date().getTime()}`;
  };
  // ...
  return htmlPlugin;
}

完善build的utils.ts

說明

完善build\utils.ts文件。

第一部分

除了這個process,應該也沒有要解釋的吧。

Node中文文檔-process

build/utils.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
export function isDevFn(mode: string): boolean {
  return mode === 'development';
}

export function isProdFn(mode: string): boolean {
  return mode === 'production';
}

/**
 * Whether to generate package preview
 */
export function isReportMode(): boolean {
  return process.env.REPORT === 'true';
}

安裝dotenv

dotenv依賴的作用:從.evn文件流Buffer中讀取屬性輸出一個對象。

那麼和我們之前的加載環境變量的區別:

  • ViteloadEnv():這個方法Vite的官網上貌似沒有開放,而且獲取的文件是指定的,獲取的對象需要我們自己格式化一下。
  • VbenwrapperEnv():這是一個格式化環境變量的方法。
  • dotenv:是一個依賴,其功能不僅僅是獲取環境變量,當然我們目前只用它來獲取指定的.env文件。

dotenv的npm首頁

1
yarn add dotenv --dev

第二部分

build/utils.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import fs from 'fs';
import path from 'path';
import dotenv from 'dotenv';

/**
 * Get the environment variables starting with the specified prefix
 * @param match prefix
 * @param confFiles ext
 */
export function getEnvConfig(match = 'VITE_GLOB_', confFiles = ['.env', '.env.production']) {
  let envConfig = {};
  // ↓ 循環文件名
  confFiles.forEach((item) => {
    try {
      // ↓ 同步讀取根目錄下指定的文件,將讀取的六傳入dotenv輸出對象
      const env = dotenv.parse(fs.readFileSync(path.resolve(process.cwd(), item)));
      // ↓ 將新獲取的對象追加到結果中,相同的覆蓋
      envConfig = { ...envConfig, ...env };
    } catch (error) {}
  });

  // ↓ 檢查輸出結果
  Object.keys(envConfig).forEach((key) => {
    // ↓ 如果不是以傳入的match變量開的就直接刪除
    const reg = new RegExp(`^(${match})`);
    if (!reg.test(key)) {
      Reflect.deleteProperty(envConfig, key);
    }
  });
  return envConfig;
}

/**
 * Get user root directory
 * @param dir file path
 */
export function getRootPath(...dir: string[]) {
  return path.resolve(process.cwd(), ...dir);
}

Vben的獲取配置文件名的方法

build/getConfigFileName.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
/**
 * Get the configuration file variable name
 * @param env
 */
export const getConfigFileName = (env: Record<string, any>) => {
  return (
    `__PRODUCTION__${env.VITE_GLOB_APP_SHORT_NAME || '__APP'}__CONF__`
      // ↓ 換成大寫
      .toUpperCase()
      // ↓ 將空白換成空字符串
      .replace(/\s/g, '')
  );
};

Vben動態配置環境變量

說明

前面章節中,我們在build\vite\plugin中註釋一段index.html中插入的script段。這一段script是用於Vben的動態配置環境變量的。

當你打包完項目之後,你的項目的環境變量就不可以變了。此時如果你想修改一下項目的基礎API路徑,那麼你需要修改.env文件,然後再次打包一遍。這麼做很麻煩。所以VbenAdmin將環境變量抽離出來。

當你的項目打包完成之後,如果你還想修改環境變量,那麼可以直接修改dist包下的_app.config.js文件。

下面看實現。

安裝fs-extra

1
yarn add fs-extra @types/fs-extra --dev

fs-extra的npm首頁fs-extra添加本機fs模塊中未包含的文件系統方法。

編寫生成配置文件的程序

Node-Api-fs

build/script/buildConf.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/**
 * Generate additional configuration files when used for packaging. The file can be configured with some global variables, so that it can be changed directly externally without repackaging
 */
import { GLOB_CONFIG_FILE_NAME, OUTPUT_DIR } from '../constant';
import fs, { writeFileSync } from 'fs-extra';
import chalk from 'chalk';

import { getRootPath, getEnvConfig } from '../utils';
import { getConfigFileName } from '../getConfigFileName';

import pkg from '../../package.json';

function createConfig(
  {
    // ↓ windows對像下的存儲配置的屬性名
    configName,
    
    // ↓ 存儲配置的對象
    config,
    
    // ↓ JS文件名
    configFileName = GLOB_CONFIG_FILE_NAME,
  }: { configName: string; config: any; configFileName?: string } = { configName: '', config: {} }
) {
  try {
    // ↓ 變量字符串
    const windowConf = `window.${configName}`;
    // Ensure that the variable will not be modified
    
    // ↓ 將配置的對像以JSON字符串格式拼接到變量字符串後面,最後用正則將空格去掉
    const configStr = `${windowConf}=${JSON.stringify(config)};
      Object.freeze(${windowConf});
      Object.defineProperty(window, "${configName}", {
        configurable: false,
        writable: false,
      });
    `.replace(/\s/g, '');
    
    // ↓ 創建dist文件夾
    fs.mkdirp(getRootPath(OUTPUT_DIR));
    
    // ↓ 將字符串寫入到dist文件下的指定JS文件名的文件中
    writeFileSync(getRootPath(`${OUTPUT_DIR}/${configFileName}`), configStr);

    console.log(chalk.cyan(`✨ [${pkg.name}]`) + ` - configuration file is build successfully:`);
    console.log(chalk.gray(OUTPUT_DIR + '/' + chalk.green(configFileName)) + '\n');
  } catch (error) {
    console.log(chalk.red('configuration file configuration file failed to package:\n' + error));
  }
}

export function runBuildConfig() {
  // ↓ 獲取我們可以配置的環境變量對象
  const config = getEnvConfig();
  
  // ↓ 獲取配置文件的JS名
  const configFileName = getConfigFileName(config);
  
  // ↓ 創建文件
  createConfig({ config, configName: configFileName });
}

安裝yargs

yargs的npm首頁:讀取你執行的命令行命令中的參數選項。

1
2
yarn add yargs --dev
yarn add @types/yargs --dev

編寫腳本觸發文件

chalk的npm首頁:一個彩色console.log的工具,這個依賴被其他依賴作為生產依賴安裝在本項目中了,所以也不去猜是哪個依賴的了,直接使用。

build/script/postBuild.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// #!/usr/bin/env node

import { argv } from 'yargs';
import { runBuildConfig } from './buildConf';
import chalk from 'chalk';

import pkg from '../../package.json';

// ↓ 定義創建配置文件的方法,最條件判斷
export const runBuild = async () => {
  try {
    // 獲取命令中不以'-'開頭的參數
    const argvList = argv._;

    // Generate configuration file
    // ↓ 如果參數中包含'no-conf',那麼就不創建配置文件
    if (!argvList.includes('no-conf')) {
      // ↓ 同步創建配置文件
      await runBuildConfig();
    }

    console.log(`✨ ${chalk.cyan(`[${pkg.name}]`)}` + ' - build successfully!');
  } catch (error) {
    console.log(chalk.red('vite build error:\n' + error));
    process.exit(1);
  }
};

// ↓ 執行方法
runBuild();

安裝esno

esno的npm首頁:命令行執行一個TS文件

1
yarn add esno --dev

編寫腳本

在編譯的時候調用TS文件:

package.json
1
2
3
4
5
6
7
{
  "scripts": {
    // ...
    "build": "vite build && esno ./build/script/postBuild.ts",
    "build-noconf": "vite build && esno ./build/script/postBuild.ts no-conf"
  }
}

index.html注入配置文件

build/vite/plugin/html.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
 * Plugin to minimize and use ejs template syntax in index.html.
 * https://github.com/anncwb/vite-plugin-html
 */
import type { Plugin } from 'vite';
import type { ViteEnv } from '../../utils';

import html from 'vite-plugin-html';

import pkg from '../../../package.json';
import { GLOB_CONFIG_FILE_NAME } from '../../constant';

export function configHtmlPlugin(env: ViteEnv, isBuild: boolean) {
  const { VITE_GLOB_APP_TITLE, VITE_PUBLIC_PATH } = env;

  const path = VITE_PUBLIC_PATH.endsWith('/') ? VITE_PUBLIC_PATH : `${VITE_PUBLIC_PATH}/`;

  const getAppConfigSrc = () => {
    return `${path || '/'}${GLOB_CONFIG_FILE_NAME}?v=${pkg.version}-${new Date().getTime()}`;
  };

  const htmlPlugin: Plugin[] = html({
    minify: isBuild,
    inject: {
      // Inject data into ejs template
      injectData: {
        title: VITE_GLOB_APP_TITLE,
      },
      // Embed the generated app.config.js file
      // ↓ 在此處插入
      tags: isBuild
        ? [
            {
              tag: 'script',
              attrs: {
                src: getAppConfigSrc(),
              },
            },
          ]
        : [],
    },
  });
  return htmlPlugin;
}

注意

之後在業務邏輯中獲取環境變量,就需要從這個JS文件中獲取了。從而達到在生產環境改配置的需求。
參考:src\utils\env.ts,但是這個後面再說。


Vite插件-vite-plugin-svg-icon

說明

當你使用該插件的時候,指定好存放svg的文件夾。再按照指定的方式去訪問svg圖片。就可以再不產生http請求的情況下渲染出svg圖片。

使用該插件時,插件會自動將所有svg圖片加載到HTML中。並且每一個svg將會被過濾去無用的信息數據。讓svg達到最小的值。之後使用svg圖片就只需要操作DOM即可,而不需要發送http請求。

安裝

1
yarn add vite-plugin-svg-icons --dev

創建配置

build/vite/plugin/svgSprite.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
/**
 *  Vite Plugin for fast creating SVG sprites.
 * https://github.com/anncwb/vite-plugin-svg-icons
 */

import SvgIconsPlugin from 'vite-plugin-svg-icons';
import path from 'path';

export function configSvgIconsPlugin(isBuild: boolean) {
  const svgIconsPlugin = SvgIconsPlugin({
    // ↓ 本地svg圖片地址
    iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
    svgoOptions: isBuild,
    // 圖標ID的樣式
    symbolId: 'icon-[dir]-[name]',
  });
  return svgIconsPlugin;
}

選項svgOptions的boolean類型不太清楚是乾什麼的。但是對像類型是控制svg過濾無用信息的選項。使用true是使用默認選項,false時不知道做什麼的但是也沒什麼影響。

用於配置

build/vite/plugin/index.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// ...
import { configSvgIconsPlugin } from './svgSprite';

export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
  // ...
  // vite-plugin-svg-icons
  vitePlugins.push(configSvgIconsPlugin(isBuild));
  // ...
  return vitePlugins;
}

main導入

src/main.ts
1
2
3
// ...
import 'vite-plugin-svg-icons/register';
// ...

創建Svg組件

這裡有一個樣式,是全局上下文注入的。這個後面再講,先寫成string

src/components/Icon/src/SvgIcon.vue
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<template>
  <svg class="vben-svg-icon" :class="[$attrs.class]" :style="getStyle" aria-hidden="true">
    <use :xlink:href="symbolId" />
  </svg>
</template>
<script lang="ts">
  import type { CSSProperties } from 'vue';
  import { defineComponent, computed } from 'vue';
  // import { useDesign } from '/@/hooks/web/useDesign';

  export default defineComponent({
    name: 'SvgIcon',
    props: {
      prefix: {
        type: String,
        default: 'icon',
      },
      name: {
        type: String,
        required: true,
      },
      size: {
        type: [Number, String],
        default: 16,
      },
    },
    setup(props) {
      // const { prefixCls } = useDesign('svg-icon');
      const symbolId = computed(() => `#${props.prefix}-${props.name}`);

      const getStyle = computed(
        (): CSSProperties => {
          const { size } = props;
          let s = `${size}`;
          s = `${s.replace('px', '')}px`;
          return {
            width: s,
            height: s,
          };
        }
      );
	  // prefixCls,
      return { symbolId, getStyle };
    },
  });
</script>
<style lang="less" scoped>
  // @prefix-cls: ~'@{namespace}-svg-icon';

  // .@{prefix-cls} {
  .vben-svg-icon {
    overflow: hidden;
    vertical-align: -0.15em;
    fill: currentColor;
  }
</style>

Vite插件-vite-plugin-windicss

說明

在Vite上單獨使用Tailwind時,渲染速度很慢。
vite-plugin-windicssTailwind快20到100倍。

安裝

1
yarn add vite-plugin-windicss --dev

創建Windicss配置文件

tailwind.config.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import lineClamp from 'windicss/plugin/line-clamp';
import colors from 'windicss/colors';

import { defineConfig } from 'vite-plugin-windicss';
import { primaryColor } from './build/config/themeConfig';

export default defineConfig({
  darkMode: 'class',
  plugins: [lineClamp, createEnterPlugin()],
  theme: {
    extend: {
      colors: {
        ...colors,
        primary: primaryColor,
      },
      screens: {
        sm: '576px',
        md: '768px',
        lg: '992px',
        xl: '1200px',
        '2xl': '1600px',
      },
    },
  },
});

/**
 * Used for animation when the element is displayed
 * @param maxOutput The larger the maxOutput output, the larger the generated css volume
 */
function createEnterPlugin(maxOutput = 10) {
  const createCss = (index: number, d = 'x') => {
    const upd = d.toUpperCase();
    return {
      [`*> .enter-${d}:nth-child(${index})`]: {
        transform: `translate${upd}(50px)`,
      },
      [`*> .-enter-${d}:nth-child(${index})`]: {
        transform: `translate${upd}(-50px)`,
      },
      [`* > .enter-${d}:nth-child(${index}),* > .-enter-${d}:nth-child(${index})`]: {
        'z-index': `${10 - index}`,
        opacity: '0',
        animation: `enter-${d}-animation 0.4s ease-in-out 0.3s`,
        'animation-fill-mode': 'forwards',
        'animation-delay': `${(index * 1) / 10}s`,
      },
    };
  };
  const handler = ({ addBase }) => {
    const addRawCss = {};
    for (let index = 1; index < maxOutput; index++) {
      Object.assign(addRawCss, {
        ...createCss(index, 'x'),
        ...createCss(index, 'y'),
      });
    }
    addBase({
      ...addRawCss,
      [`@keyframes enter-x-animation`]: {
        to: {
          opacity: '1',
          transform: 'translateX(0)',
        },
      },
      [`@keyframes enter-y-animation`]: {
        to: {
          opacity: '1',
          transform: 'translateY(0)',
        },
      },
    });
  };
  return { handler };
}

創建配置文件

build/vite/plugin/windicss.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import type { Plugin } from 'vite';

import windiCSS from 'vite-plugin-windicss';

export function configWindiCssPlugin(): Plugin[] {
  return windiCSS({
    safelist: 'no-select',
    preflight: {
      enableAll: true,
    },
  });
}

應用配置

build/vite/plugin/index.ts
1
2
3
4
5
6
7
8
9
// ...
import { configWindiCssPlugin } from './windicss';

export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
  // ...
  // vite-plugin-windicss
  vitePlugins.push(configWindiCssPlugin());
  return vitePlugins;
}

導入樣式

src/main.ts
1
2
3
// ...
import '@virtual/windi.css';
// ...

Vite插件-vite-plugin-mock

說明

vite-plugin-mock的npm首頁

根據我自己的實驗:

localEnabled控制mock開發環境是否啟動。
如果生產環境想要使用mock,只有prodEnabled為true,injectCode注入指定代碼時才會生效。

安裝

1
yarn add mockjs vite-plugin-mock --dev

編寫Mock用例

這裡直接將根目錄下的mock目錄拷貝出來就行。

  • mock\_util.ts:裡面封裝的是數據請求結構類型。
  • mock\_createProductionServer.ts:用於配置生產環境動態Mock的js文件,文檔中有說。
  • 其他的:都是Mock用例,每一個jsts,都要默認導出一個MockMethod類型的數組。每一項MockMethod就是攔截的一個方法。MockMethodresponse對應方法的return將會被Mock實例處理。也就是說,你可以在return的對像中使用Mock規則

不過_createProductionServer.ts中使用了Glob 導入

配置Mock

build/vite/plugin/mock.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
/**
 * Mock plugin for development and production.
 * https://github.com/anncwb/vite-plugin-mock
 */
import { viteMockServe } from 'vite-plugin-mock';

export function configMockPlugin(isBuild: boolean) {
  return viteMockServe({
    // ↓ 忽略以_開頭的文件
    ignore: /^\_/,

    // ↓ 解析根目錄下的mock文件夾
    mockPath: 'mock',
    localEnabled: !isBuild,
    prodEnabled: isBuild,
    injectCode: `
      import { setupProdMockServer } from '../mock/_createProductionServer';
      setupProdMockServer();
      `,
  });
}

配置進Vite

build/vite/plugin/index.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// ...
import { configMockPlugin } from './mock';

export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean, pkg: any) {
  // ...
  // vite-plugin-mock
  VITE_USE_MOCK && vitePlugins.push(configMockPlugin(isBuild));

  return vitePlugins;
}

Vite插件-vite-plugin-purge-icons

說明

本章的插件是可以讓我們很方便高效的使用Iconify中所有的圖標。

這裡要講的是Iconify各個版本插件的區別:

  • Vue3版Iconify插件:使用時需要安裝指定庫的圖標,然後靜態引用。每一次引用都會產生一次http請求。
  • PurgeIcons:將我們所使用的Iconify圖標都已htmldom節點形式保存在html中,這樣我們就可以不發送http請求就可以使用圖標了。
  • vite-plugin-purge-icons:就是Vite版的PurgeIcons

vite-plugin-purge-icons的npm首頁

安裝

1
2
yarn add @iconify/iconify
yarn add vite-plugin-purge-icons @iconify/json --dev

配置Vite

build/vite/plugin/index.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// ...
import PurgeIcons from 'vite-plugin-purge-icons';

export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
  // ...
  // vite-plugin-purge-icons
  vitePlugins.push(PurgeIcons());

  // ...
  return vitePlugins;
}

注意

Icon組件後面再封裝,由於我們main.ts中沒有引入vite-plugin-purge-icons。所以,這裡還不能使用。後面會動態生成圖標。


Vite插件-vite-plugin-style-import

說明

vite-plugin-style-import的npm首頁

該插件可按需導入組件庫樣式,由於 vite 本身已按需導入了組件庫,因此僅樣式不是按需導入的,因此只需按需導入樣式即可。

安裝

1
yarn add vite-plugin-style-import --dev

配置插件

build/vite/plugin/styleImport.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
/**
 *  Introduces component library styles on demand.
 * https://github.com/anncwb/vite-plugin-style-import
 */
import styleImport from 'vite-plugin-style-import';

export function configStyleImportPlugin() {
  // if (!isBuild) return [];
  const pwaPlugin = styleImport({
    libs: [
      {
        libraryName: 'ant-design-vue',
        esModule: true,
        resolveStyle: (name) => {
          return `ant-design-vue/es/${name}/style/index`;
        },
      },
    ],
  });
  return pwaPlugin;
}

配置Vite

build/vite/plugin/index.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// ...
import { configStyleImportPlugin } from './styleImport';

export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean, pkg: any) {
  // ...
  // vite-plugin-style-import
  vitePlugins.push(configStyleImportPlugin());

  return vitePlugins;
}

Vite插件-rollup-plugin-visualizer

說明

安裝

1
yarn add rollup-plugin-visualizer @types/rollup-plugin-visualizer --dev

配置插件

build/vite/plugin/visualizer.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
/**
 * Package file volume analysis
 */
import visualizer from 'rollup-plugin-visualizer';
import { isReportMode } from '../../utils';
import type { Plugin } from 'vite';

export function configVisualizerConfig() {
  if (isReportMode()) {
    return visualizer({
      filename: './node_modules/.cache/visualizer/stats.html',
      open: true,
      // @ts-ignore
      gzipSize: true,
      // @ts-ignore
      brotliSize: true,
    }) as Plugin;
  }
  return [];
}

配置Vite

build/vite/plugin/index.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// ...
import { configVisualizerConfig } from './visualizer';

export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean, pkg: any) {
  // ...
  // rollup-plugin-visualizer
  vitePlugins.push(configVisualizerConfig());

  return vitePlugins;
}

安裝cross-env

1
yarn add cross-env --dev

編輯腳本

package.json
1
2
3
4
5
6
7
8
9
{
  // ...
  "scripts": {
    // ...
    "report": "cross-env REPORT=true npm run build"
    // ...
  }
  // ...
}

Vite插件-vite-plugin-theme

說明

vite-plugin-theme的npm首頁Git站點,npm的失效了。

用於動態更改界面主題色的 vite 插件。

配置插件

build/vite/plugin/theme.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
/**
 * Vite plugin for website theme color switching
 * https://github.com/anncwb/vite-plugin-theme
 */
import { viteThemePlugin, mixLighten, mixDarken, tinycolor } from 'vite-plugin-theme';
import { getThemeColors, generateColors } from '../../config/themeConfig';

export function configThemePlugin() {
  const colors = generateColors({
    mixDarken,
    mixLighten,
    tinycolor,
  });

  const plugin = viteThemePlugin({
    // ↓ 之前生成的很多個顏色
    colorVariables: [...getThemeColors(), ...colors],
  });
  return plugin;
}

配置Vite

build/vite/plugin/index.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// ...
import { configThemePlugin } from './theme';

export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean, pkg: any) {
  // ...
  //vite-plugin-theme
  vitePlugins.push(configThemePlugin());

  return vitePlugins;
}

修改主題方法

之後要修改主題,直接調用一下這個方法即可。

src/logics/theme/index.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import { getThemeColors, ThemeMode, generateColors } from '../../../build/config/themeConfig';

import { replaceStyleVariables } from 'vite-plugin-theme/es/client';
import { mixLighten, mixDarken, tinycolor } from 'vite-plugin-theme/es/colorUtils';

export async function changeTheme(color: string, theme?: ThemeMode) {
  const colors = generateColors({
    mixDarken,
    mixLighten,
    tinycolor,
    color,
  });

  return await replaceStyleVariables({
    colorVariables: [...getThemeColors(color, theme), ...colors],
  });
}

Vite插件-vite-plugin-imagemin

說明

vite-plugin-imagemin的npm首頁:一個壓縮圖片資源的 vite 插件。

配置鏡像(用於安裝imagemin的依賴,因為中國可能沒有安裝imagemin。)

若非中國用戶,可跳過配置鏡像

package.json
1
2
3
4
"resolutions": {
    "//": "用於安裝imagemin的依賴關係,因為中國可能沒有安裝imagemin。",
    "bin-wrapper": "npm:bin-wrapper-china"
},

安裝

1
yarn add vite-plugin-imagemin --dev

配置插件

build/vite/plugin/imagemin.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// Image resource files used to compress the output of the production environment
// https://github.com/anncwb/vite-plugin-imagemin
import viteImagemin from 'vite-plugin-imagemin';

export function configImageminPlugin() {
  const plugin = viteImagemin({
    gifsicle: {
      optimizationLevel: 7,
      interlaced: false,
    },
    optipng: {
      optimizationLevel: 7,
    },
    webp: {
      quality: 75,
    },
    mozjpeg: {
      quality: 8,
    },
    pngquant: {
      quality: [0.8, 0.9],
      speed: 4,
    },
    svgo: {
      plugins: [
        {
          removeViewBox: false,
        },
        {
          removeEmptyAttrs: false,
        },
      ],
    },
  });
  return plugin;
}

配置Vite

build/vite/plugin/index.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// ...
import { configImageminPlugin } from './imagemin';

export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean, pkg: any) {
  // ...
  if (isBuild) {
    //vite-plugin-imagemin
    VITE_USE_IMAGEMIN && vitePlugins.push(configImageminPlugin());
  }

  return vitePlugins;
}

Vite插件-vite-plugin-compression

說明

vite-plugin-compression的npm首頁vite-plugin-compress的增強版,壓縮用的。

安裝

1
yarn add vite-plugin-compression --dev

配置插件

build/vite/plugin/compress.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
 * Used to package and output gzip. Note that this does not work properly in Vite, the specific reason is still being investigated
 * https://github.com/anncwb/vite-plugin-compression
 */
import type { Plugin } from 'vite';

import compressPlugin from 'vite-plugin-compression';

export function configCompressPlugin(compress: 'gzip' | 'brotli' | 'none'): Plugin | Plugin[] {
  const compressList = compress.split(',');

  const plugins: Plugin[] = [];

  if (compressList.includes('gzip')) {
    plugins.push(
      compressPlugin({
        ext: '.gz',
      })
    );
  }
  if (compressList.includes('brotli')) {
    plugins.push(
      compressPlugin({
        ext: '.br',
        algorithm: 'brotliCompress',
      })
    );
  }
  return plugins;
}

配置Vite

build/vite/plugin/index.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// ...
import { configCompressPlugin } from './compress';

export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean, pkg: any) {
  // ...
  // The following plugins only work in the production environment
  if (isBuild) {
    // ...
    // rollup-plugin-gzip
    vitePlugins.push(configCompressPlugin(VITE_BUILD_COMPRESS));
  }

  return vitePlugins;
}

Vite插件-vite-plugin-pwa

說明

安裝

1
yarn add vite-plugin-pwa --dev

配置插件

build/vite/plugin/pwa.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
 * Zero-config PWA for Vite
 * https://github.com/antfu/vite-plugin-pwa
 */
import type { ViteEnv } from '../../utils';

import { VitePWA } from 'vite-plugin-pwa';

export function configPwaConfig(env: ViteEnv) {
  const { VITE_USE_PWA, VITE_GLOB_APP_TITLE, VITE_GLOB_APP_SHORT_NAME } = env;

  if (VITE_USE_PWA) {
    // vite-plugin-pwa
    const pwaPlugin = VitePWA({
      manifest: {
        name: VITE_GLOB_APP_TITLE,
        short_name: VITE_GLOB_APP_SHORT_NAME,
        icons: [
          {
            // ./表示public文件夾
            src: './resource/img/pwa-192x192.png',
            sizes: '192x192',
            type: 'image/png',
          },
          {
            src: './resource/img/pwa-512x512.png',
            sizes: '512x512',
            type: 'image/png',
          },
        ],
      },
    });
    return pwaPlugin;
  }
  return [];
}

配置Vite

build/vite/plugin/index.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// ...
import { configPwaConfig } from './pwa';

export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean, pkg: any) {
  // ...
  // The following plugins only work in the production environment
  if (isBuild) {
    // ...
    // vite-plugin-pwa
    vitePlugins.push(configPwaConfig(viteEnv));
  }

  return vitePlugins;
}

Vite配置-optimizeDeps.include

說明

Vite配置-optimizedeps-include說明

原文:
類型: string[]
默認情況下,不在 node_modules 中的,鏈接的包不會被預構建。使用此選項可強制預構建鏈接的包。

安裝moment

antd中需要使用moment。而moment配置和本章有關,所以就順便安裝一下:

1
yarn add moment

配置Vite

vite.config.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// ...
function pathResolve(dir: string) {
  return resolve(__dirname, '.', dir);
}

export default ({ command, mode }: ConfigEnv): UserConfig => {
  // ...
  return {
    // ...
    optimizeDeps: {
      // @iconify/iconify: The dependency is dynamically and virtually loaded by @purge-icons/generated, so it needs to be specified explicitly
      include: [
        '@iconify/iconify',
        'ant-design-vue/es/locale/zh_CN',
        'moment/dist/locale/zh-cn',
        'ant-design-vue/es/locale/en_US',
        'moment/dist/locale/eu',
      ],
      // ...
    },
  };
};

Vite配置-optimizeDeps.exclude

說明

Vite配置-optimizedeps-exclude說明

原文:
類型: string[]
在預構建中強制排除的依賴項。

配置Vite

vite.config.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// ...

export default ({ command, mode }: ConfigEnv): UserConfig => {
  // ...

  return {
    // ...
    optimizeDeps: {
      // ...
      exclude: ['vue-demi'],
    },
  };
};

http-server

說明

項目編譯之後的靜態文件是不能直接本地訪問的。因為本地訪問使用的是file:///協議。而file:///不支持跨域和一些其他特性。比如JavaScript模塊、PWA等等。
那麼此時就需要換一種訪問本地文件的方式了,就是讓本地成為一個服務器。通過http來訪問。

http-server就可以實現以http形式訪問本地文件的目的,但是這個依賴不保證安全,只用於本地測試。
http-server的npm首頁:命令行啟動一個本地服務器。不安全,但是可以用來測試。

安裝

1
yarn add http-server --dev

配置腳本

package.json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  // ...
  "scripts": {
    // ...
    "test:gzip": "http-server dist --cors --gzip -c-1",
    "test:br": "http-server dist --cors --brotli -c-1",
    // ...
  },
  // ...
}

comments powered by Disqus