全網最硬核的Ant-Design-Vue從Vue-cli遷移至Vite

silianpan 發表於 2022-05-19
Vue

一、前言

眾所周知,Vite作為下一代前端開發與構建工具,就是一個字:快。並且Vite已經作為Vue3預設的構建工具。通過實驗表明,專案遷移後,從Vue-cli的近2分鐘,到Vite的5秒(專案大小不同,時間也不同),提升了幾十倍甚至上百倍的速度。

本文針對老專案從Vue-cli遷移到Vite,提供了全網最全的方案。下面以ant-design-vue-pro為例進行遷移,ant-design-vue版本為1.7.8。

同時,提供了遷移後的倉庫,歡迎Star~

GitHub - Seals-Studio/ant-design-vue-pro-vite

遷移前後對比(參考)

構建工具伺服器啟動耗時頁面首次載入速度 (無快取)第二次載入速度 (有快取)熱更新 HMR打包
Webpack83s4.78s3.35s4.78s3mins 37s
Vite4.72s (第二次 0.72s)1.71s1.33s瞬間51.45s

二、刪除package.json相關依賴

  1. 刪除@vue和babel相關

    {
        "@vue/babel-helper-vue-jsx-merge-props": "^1.2.1",
        "@vue/cli-plugin-babel": "^4.5.17",
        "@vue/cli-plugin-eslint": "^4.5.17",
        "@vue/cli-plugin-router": "^4.5.17",
        "@vue/cli-plugin-unit-jest": "^4.5.17",
        "@vue/cli-plugin-vuex": "^4.5.17",
        "@vue/cli-service": "^4.5.17",
        "@vue/eslint-config-standard": "^4.0.0",
        "@vue/test-utils": "^1.3.0",
        "babel-eslint": "^10.1.0",
        "babel-plugin-import": "^1.13.3",
        "babel-plugin-transform-remove-console": "^6.9.4",
    }
  2. 刪除loader(webpack外掛)和webpack

    {
        "file-loader": "^6.2.0",
        "less-loader": "^5.0.0",
        "vue-svg-icon-loader": "^2.1.1",
        "git-revision-webpack-plugin": "^3.0.6",
        "webpack-theme-color-replacer": "^1.3.26",
    }
  3. 刪除babel.conf.jsjsconfig.json
  4. 安裝pnpm工具
pnpm是快速的,節省磁碟空間的包管理工具
npm i -g pnpm
# 淘寶源
pnpm config set registry https://registry.npm.taobao.org  
pnpm config set disturl https://npm.taobao.org/dist
pnpm config set NVM_NODEJS_ORG_MIRROR http://npm.taobao.org/mirrors/node  
pnpm config set NVM_IOJS_ORG_MIRROR http://npm.taobao.org/mirrors/iojs  
pnpm config set PHANTOMJS_CDNURL https://npm.taobao.org/dist/phantomjs  
pnpm config set ELECTRON_MIRROR http://npm.taobao.org/mirrors/electron/  
pnpm config set SASS_BINARY_SITE http://npm.taobao.org/mirrors/node-sass  
pnpm config set SQLITE3_BINARY_SITE http://npm.taobao.org/mirrors/sqlite3  
pnpm config set PYTHON_MIRROR http://npm.taobao.org/mirrors/python

三、安裝最新版vitevite-plugin-vue2

pnpm add vite vite-plugin-vue2 -D

四、在根目錄下新建vite.conf.js

import { defineConfig } from 'vite'
// vue2的vite外掛
import { createVuePlugin } from 'vite-plugin-vue2'

export default ({ mode }) => {
  return defineConfig({
    plugins: [
      createVuePlugin({
        jsx: true
      })
    ]
  })
})

五、index.html修改

  • 移動public/index.html到程式碼根目錄(和package.json同級)
  • 在body標籤中新增如下:

    <!-- 指明載入main.js -->
    <script type="module" src="/src/main.js"></script>
  • 替換htmlWebpackPlugin外掛注入的變數

    htmlWebpackPlugin是webpack外掛,所以不能再使用了,vite提供了vite-plugin-html外掛來向index.html注入變數
    1. 安裝vite-plugin-html

      pnpm add vite-plugin-html -D
    2. 修改vite.config.js,新增配置
       plugins: [
          // ...
          createHtmlPlugin({
            minify: true,
            inject: {
              data: {
                title: 'Ant Design Pro',
                cdn: {
                  css: [],
                  js: [
                        '//cdn.jsdelivr.net/npm/[email protected]/dist/vue.min.js',
                        '//cdn.jsdelivr.net/npm/[email protected]/dist/vue-router.min.js',
                        '//cdn.jsdelivr.net/npm/[email protected]/dist/vuex.min.js',
                        '//cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js'
                  ]
                }
              }
            }
          }),
          // ...
        ]
    1. 修改index.html

      • 修改title

        <title><%= title %></title>
      • 修改css和js引入

            <!-- require cdn assets css -->
            <% for (var i in cdn.css) { %>
            <link rel="stylesheet" href="<%= cdn.css[i] %>" />
            <% } %>
        
            <!-- require cdn assets js -->
            <% for (var i in cdn.js) { %>
            <script type="text/javascript" src="<%= cdn.js[i] %>"></script>
            <% } %>

六、環境變數更換

出於安全考慮,vite只能識別以VITE_開頭的環境變數了,原VUE_環境變數不生效了,同時,也不能使用process.env.xxx來讀取環境變數了。需要修改vite.conf.js配置,手動新增process.env.xxx環境變數
  • 修改vite.conf.js配置,新增環境變數

    import { defineConfig, loadEnv } from 'vite'
    
    export default ({ mode }) => {  
      const env = loadEnv(mode, process.cwd())
      return defineConfig({
        define: {
          'process.env': { ...env }
        },
      })
    })
  • 將所有開頭的VUE_環境變數全部替換為VITE_
  • 將所有的process.env.NODE_ENV更改為import.meta.env.MODE
  • 將所有開頭為process.env.全部更改為import.meta.env.

七、Ant-Design-Vue按需引入

  1. 安裝vite-plugin-style-import外掛
# 注意本外掛必須採用1.4.1版本,不能採用最新版2.0.0
pnpm add [email protected]^1.4.1 -D
  1. 增加vite.conf.js配置

    plugins: [
        // ...
        styleImport({
            libs: [
              {
                libraryName: 'ant-design-vue',
                esModule: true,
                resolveStyle: (name) => {
                  return `ant-design-vue/es/${name}/style/index`
                },
              }
            ],
          }),
        // ...
    ]

八、Ant-Design-Vue引入moment問題

原因是antdv底層引入採用:import * as moment from "moment";

未相容ESM寫法,參考 github issue: chore: v1 support vite

本文參考[]外掛,寫了一個vite外掛,去修改antdv底層引入moment方式,改為:import moment from moment

  1. 安裝依賴包

    pnpm add [email protected]">=1.20.0 <2.0.0 || >=2.0.0 <3.0.0" -D
    pnpm add @rollup/plugin-replace -D
  2. 修改vite.conf.js配置
import path from 'path-browserify'
import fs from 'fs'
import replace from '@rollup/plugin-replace'


// 參考vite-plugin-antdv1-momentjs-resolver外掛,修改正規表示式,相容Windows路徑
// https://github.com/carl-jin/vite-plugin-antdv1-momentjs-resolver/blob/main/src/index.js
// 將moment_util.js中import * as moment from moment修改import moment from moment
// 原正規表示式
// const antdvDefaultReg = /ant-design-vue\/[\w-\\\/]*\.js$/
// 修改後正規表示式
// const antdvDefaultReg = /ant-design-vue[\/|\\][\w-\\\/]*\.js$/
const AntdMomentResolver = (reg = /ant-design-vue[\/|\\][\w-\\\/]*\.js$/) => {
  return {
    name: 'vite-plugin-antdv1-momentjs-resolver',
    configResolved(config) {
      //  以來預構建時候替換 esbuild
      config.optimizeDeps.esbuildOptions.plugins = config.optimizeDeps.esbuildOptions.plugins
        ? config.optimizeDeps.esbuildOptions.plugins
        : []
      config.optimizeDeps.esbuildOptions.plugins.push({
        name: 'replace-code',
        setup(build) {
          build.onLoad(
            {
              filter: reg,
            },
            (args) => {
              // 首先獲取原始碼內容
              let source = fs.readFileSync(args.path, 'utf8')
              if (source.indexOf('import * as moment from')) {
                source = source.replace(/import\s\*\sas\smoment\sfrom/g, 'import moment from')
              }
              return {
                contents: source,
              }
            }
          )
        },
      })

      //  新增打包時的替換 rollup
      config.plugins.push(
        replace({
          values: {
            'import * as moment from': (id) => {
              return 'import moment from'
            },
          },
          include: [reg],
          preventAssignment: true,
        })
      )
    },
  }
}

// 引入外掛
export default ({ mode }) => {
    return defineConfig({
    plugins: [
          // ...
          AntdMomentResolver(),
          // ...
    ]
   })
}

九、新增代理

  1. 安裝path-browserify

    pnpm add path-browserify -D
  2. 新增vite.conf.js配置

        plugin: [],
        // ...
        server: {
          port: 8000,
          //proxy: {
          //  '/api': {
          //    target: 'https://mock.ihx.me/mock/5baf3052f7da7e07e04a5116/antd-pro',
          //    changeOrigin: true,
          //    ws: false,
          //    rewrite: (path) => path.replace(/^\/api/, ''),
          //  }
          //},
        },

十、package.json指令碼命令修改

將指令碼命令修改為如下:

  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },

十一、postcss配置

  1. 安裝外掛

    pnpm add postcss autoprefixer -D

十二、新增eslint外掛

  1. 安裝外掛

    pnpm remove eslint eslint-plugin-html eslint-plugin-vue
    pnpm add eslint eslint-plugin-html eslint-plugin-vue eslint-config-prettier eslint-plugin-prettier prettier -D
    # vite-eslint外掛
    pnpm add vite-plugin-eslint -D
  2. 新增vite.conf.js配置

    import eslintPlugin from 'vite-plugin-eslint'
    
    export default ({ mode }) => {
        return defineConfig({
        plugins: [
            // ...
            eslintPlugin(),
            // ...
        ]
       })
    }

十三、在寫有jsx語法的檔案中新增lang="jsx"

<script lang="jsx">
    ...
</script>

十四、新增@別名

修改vite.conf.js配置

export default ({ mode }) => {
    return defineConfig({
       resolve: {
       // ...
         alias: [
           {
              find: /@\/.+/,
              replacement: (val) => {
                  return val.replace(/^@/, path.resolve(__dirname, './src/'))
              },
           },
         ]
       },
    )
}

十五、靜態檔案引入

  1. 動態元件引入

    const modules = import.meta.glob('../views/**/*.vue')
    
    const currentRouter {
        ...
        // component: constantRouterComponents[item.component || item.key] || (() => import(`/src/views/${item.component}`)),
        component: constantRouterComponents[item.component || item.key] || modules[`../views/${item.component}.vue`],
        ...
    }
    
    
  2. 靜態圖片引入

    • 直接import圖片

      <template>
          <img :src="LogoImg" />
      </template>
      
      <script>
          import LogoImg from '/src/assets/img/logo.svg'
          export default {
              data() {
                  return {
                      LogoImg
                  }
              }
          }
      </script>
    • 採用import.meta.globEager

      <template>

      <img :src="getImg('../../assets/img/log.svg')" />

      </template>

      <script>

      export default {
          methods: {
              getImg(path) {
                  const modules = import.meta.globEager('../../assets/img/*.svg')
                  return modules[path].default
              }
          }
      }

      </script>

    
    ### 十六、其他
  3. 問題:fim.js依賴包引用問題

    解決:刪除viser-vue依賴包,可以改用官方G2的封裝庫@antv/g2plot

    pnpm remove viser-vue
    pnpm add @antv/g2plot
  4. 問題:ant-design-vue元件List引用問題,List.Item為undefined

    解決一:替換程式碼

     // 替換List元件程式碼,List.Item為undefined
     if (source.indexOf('Vue.component(List.Item.name, List.Item);')) {
        source = source.replace(
          'Vue.component(List.Item.name, List.Item);',
          'Vue.component("AListItem", Item);'
        )
     }
     if (source.indexOf('Vue.component(List.Item.Meta.name, List.Item.Meta);')) {
        source = source.replace(
          'Vue.component(List.Item.Meta.name, List.Item.Meta);',
           'Vue.component("AListItemMeta", Item.Meta);'
     )
    }               

    解決二:單獨引用List.Item