【前端工程化】vue2+webpack3老项目迁移vite2流程和踩坑总结

3,448 阅读7分钟

目录

  1. 前言
  2. 迁移前后对比
  3. 迁移流程
  4. 迁移业务代码到vite项目
  5. 项目开发阶段报错处理
  6. 项目打包阶段处理
  7. 总结

一. 前言

公司有个特别大维护时间长的后台管理系统,使用的是vue2webpack3,虽然配置了很多编译优化,但启动和开发热更新速度依然很慢,极大的影响了开发效率,于是准备迁移vite2来优化,现在已迁移升级,来复盘记录下过程,至于vite速度快于webpack的原因,已经有很多文章讲的很明白,这里只记录下迁移过程和遇到的问题。

二. 迁移前后对比

对比迁移前webpack3迁移后vite2
启动开发模式40秒3秒之内
热更新6秒+1秒之内
打包构建(vite不做低版本浏览器兼容)2分30秒40秒
打包构建(vite做低版本浏览器兼容)2分30秒1分05秒

三. 迁移流程

  1. 先创建新的vite项目
  2. 新版vite项目默认是支持vue3的,需要把vue改成vue2版本后配置vite-plugin-vue2插件来支持vue2
  3. 把项目代码改成vue2写法,确保新vite项目可以正常运行vue2
  4. 把原webpack项目生产环境依赖复制到vite项目,剔除掉webpack相关的插件依赖
  5. 复制原项目src文件代码和其他业务相关代码到新vite项目。
  6. vite项目配置开发环境启动命令,根据报错信息来进行调整。
  7. 在测试开发和打包环境都没问题后,替换原先的项目。

四. 迁移业务代码到vite项目

4.1 创建新的vite项目

打开vite官网,按照官网提示创建新的vite项目(由于原先项目没有用ts,所以创建项目不选ts版本,包管理工具也依然选择是npm)。

npm init vite@latest my-vue-app -- --template vue

创建完成后,使用vs code打开,打开命令行,执行npm i安装依赖

npm i

安装依赖完成后,使用npm run dev启动项目

npm run dev

启动完成后,打开项目地址http://localhost:3000/

WX20220428-223833@2x.png

此时基本的vite2+vue3项目已经启动成功了,但此时vite支持的还是vue3版本的,我们需要让vite支持vue2版本。

4.2 配置vite支持vue2

此时打开vite.config.js,里面的代码为

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()]
})

@vitejs/plugin-vue插件是对vue3语法做支持,如果要支持vue2,需要用vite-plugin-vue2

第一步,从vite中删除 @vitejs/plugin-vue配置,从package.json文件中也删除。

npm uninstall @vitejs/plugin-vue -D

第二步,安装vite-plugin-vue2依赖

npm install vite-plugin-vue2 -D

第三步,在vite.config.js文件配置vite-plugin-vue2

import { defineConfig } from 'vite'
import { createVuePlugin } from "vite-plugin-vue2";
​
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [createVuePlugin()]
})

第四步,修改vue版本由3改为2版本

npm install vue@2 -S

第五步, 修改main.js,创建根vue实例写法改为vue2写法

import Vue from 'vue'
import App from './App.vue'new Vue({
  render: h => h(App),
}).$mount('#app')

第六步,修改main.js完成后,修改App.vue文件代码为vue2格式代码

<template>vue
  <h1>{{title}}</h1>
</template>
​
<script>
export default {
  name: 'App',
  data() {
    return {
      title: 'webpack3迁移vite2'
    }
  }
}
</script>

此时package.json, vite.config.js, main.js, App.vue代码分别为

WX20220428-225939@2x.png

执行npm run dev,即可看到启动成功,代表此时vite已经支持vue2语法了,可以开始项目迁移工作了。

4.3 复制原项目业务代码

第一步,复制原项目静态目录static下文件到vite项目public文件夹下

第二步,复制原项目index.html文件内容替换vite项目的index.html内容(注意本地静态资源引入的路径)替换后需要在body结束标签前添加 <script type="module" src="/src/main.js"></script>

<!DOCTYPE html>
<html><head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1.0">
  <title>运营平台</title>
  <link rel="icon" href="/favicon.ico" type="image/x-icon">
  <link href="https://unpkg.com/ionicons@4.2.2/dist/css/ionicons.min.css" rel="stylesheet">
</head>
<body>
  <div id="app"></div>
  <!-- built files will be auto injected -->
  <script src="https://g.alicdn.com/dingding/dinglogin/0.0.5/ddLogin.js"></script>
  <script type="module" src="/src/main.js"></script>
</body></html>

第三步, 复制package.json中生产环境依赖到新vite项目,去除webpack相关配置依赖,此时最新的vite代码

{
  "name": "my-vue-app",
  "private": true,
  "version": "0.0.0",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "vue": "^2.6.14",
    "axios": "^0.18.0",
    "babel-polyfill": "^6.26.0",
    "dayjs": "^1.7.8",
    "dingtalk-jsapi": "^2.7.3",
    "element-ui": "^2.13.1",
    "good-storage": "^1.0.1",
    "js-base64": "^2.4.9",
    "moment": "^2.23.0",
    "nprogress": "^0.2.0",
    "qs": "^6.5.2",
    "sockjs-client": "^1.3.0",
    "stompjs": "^2.3.3",
    "swiper": "^4.3.5",
    "v-viewer": "^1.2.1",
    "vue-router": "^3.0.1",
    "vue-virtual-scroller": "^1.0.10",
    "vuex": "^3.0.1"
  },
  "devDependencies": {
    "vite": "^2.9.2",
    "vite-plugin-vue2": "^2.0.0"
  }
}

第四步,复制原项目src业务文件代码,直接替换vite项目src文件,此时整体项目结构如下

WX20220428-231642@2x.png

4.4 配置vite的项目环境变量

由于原项目使用了cross-env来设置环境变量,为了可以正常启动项目,需要在vite里面也配置下环境变量

第一步,安装cross-env

npm install cross-env -D

第二步,配置scripts脚步命令,使用cross-env来设置环境变量BASE_ENV(这一步要按自己项目情况来配置)

"scripts": {
  "dev:dev": "cross-env BASE_ENV=development vite",
  "dev:test": "cross-env BASE_ENV=test vite",
  "dev:pre": "cross-env BASE_ENV=pre vite",
  "dev:prod": "cross-env BASE_ENV=production vite",
  "build:dev": "node node_modules/esbuild/install.js && cross-env BASE_ENV=development vite build",
  "build:test": "node node_modules/esbuild/install.js && cross-env BASE_ENV=test vite build",
  "build:pre": "node node_modules/esbuild/install.js && cross-env BASE_ENV=pre vite build",
  "build:prod": "node node_modules/esbuild/install.js && cross-env BASE_ENV=production vite build",
  "preview": "vite preview"
}
  1. 打包添加node node_modules/esbuild/install.js,是用来解决esbuild安装时报错的bug相关issue
  2. 之所以环境变量没有使用NODE_ENV,是因为很多第三方包都使用NODE_ENV来判断开发环境和打包环境,为了不影响,采用新加一个环境变量的方式

第三步, 配置vite.config.js,使vite在项目里面注入环境变量

只是使用cross-env设置环境变量的话,项目里面是访问不到的,需要配置一下vitedefine配置,才能访问到,类似于webpackDefinePlugin插件功能

import { defineConfig } from 'vite'
import { createVuePlugin } from "vite-plugin-vue2";
​
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [createVuePlugin()],
  define: {
    /** 项目环境变量 **/
    'process.env.BASE_ENV': JSON.stringify(process.env.BASE_ENV)
  },
})

此时前期准备工作都已经完成,可以开始启动项目了,肯定会有很多报错,需要根据报错信息来进行调整

五. 项目开发阶段报错处理

先根据上面配置好的命令,启动开发模式

npm run dev:dev

5.1 第一个报错,The following dependencies are imported but could not be resolved:

WX20220428-233659@2x.png

由提示信息很容易可以看出,是由于原项目使用webpack配置了别名,但新vite项目未配置别名造成的,我们需要在vite中添加别名配置

根据vite别名规则,vite.config.js添加配置,把@/指向src目录

import { defineConfig } from 'vite'
import { createVuePlugin } from "vite-plugin-vue2";
​
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [createVuePlugin()],
  define: {
    /** 项目环境变量 **/
    'process.env.BASE_ENV': JSON.stringify(process.env.BASE_ENV)
  },
  resolve: {
    /** 添加alias规则 **/
    alias: [
      {
        find: '@/',
        replacement: '/src/'
      }
    ],
  },
})

添加后再次重启项目,出现第二个报错

5.2 第二个报错, Failed to resolve import "./App" from "src/main.js

WX20220428-235908@2x.png

报错是由于引入App组件的时候没有带文件后缀 .vue, 所以未找到,此时有两种解决方案

  1. 手动添加 .vue后缀,但是项目这么庞大,很多地方都没有带后缀,全部改肯定不容易。
  2. 配置vite.config.jsextensions字段,来添加自动查找文件扩展名后缀。

采用第二种vite配置extensions扩展名,在vite.config.js里面添加resolve.extensions配置

import { defineConfig } from 'vite'
import { createVuePlugin } from "vite-plugin-vue2";
​
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [createVuePlugin()],
  define: {
    /** 项目环境变量 **/
    'process.env.BASE_ENV': JSON.stringify(process.env.BASE_ENV)
  },
  resolve: {
    /** 添加alias规则 **/
    alias: [
      {
        find: '@/',
        replacement: '/src/'
      }
    ],
    /** 暂时先加.vue, .js, .json **/
    extensions: [".vue", ".js", ".json"],
  },
})

添加后再次重启项目,出现第三个报错

5.3 第三个报错, dependency "sass" not found. Did you install it

WX20220429-000755@2x.png

由报错信息可知,是因为项目里面使用了sass,less,但是没有对应的解析配置,需要添加一下vite解析sass,less的配置

安装vite中解析sass, less插件

npm install less sass -D

vite中只需要安装sass,less插件就可以了,vite会自动使用该插件去处理sass,less文件

添加后再次重启项目,出现第四个报错

5.4 第四个报错, Can't find stylesheet to import.

WX20220429-002105@2x.png

看报错信息是在配置element-ui自定义主题色的时候,在element-variables.scss文件中使用 @importelement-ui的主题文件,使用~前缀,而vite解析不了,所以没有找到对应的文件。

需要把前缀~去掉,直接去引用node_modules下的主题样式文件即可,但是由于element-ui使用calc语法,在sass2.0.0版本会删除对该方法的支持,所以控制台会报警告:

Deprecation Warning: Using / for division outside of calc() is deprecated and will be removed in Dart Sass 2.0.0.

不影响正常项目运行,如果要解决的话,降低sass的版本就可以了,目前sass版本是1.51.1,手动降版本

npm install sass@1.30.0 -D

添加后再次重启项目, 控制台就不会出现警告了。

5.5 第五个报错, require is not defined

这次启动项目后,命令行没有报错了,然后打开浏览器,发现页面白屏,打开控制台看到控制台报错

token.js:18 Uncaught ReferenceError: require is not defined
    at token.js

打开对应的token.js使用reuire引入了一张图片,而vite不支持require,我们需要换一种引入方式来引入图片。

有三种方案:

1. 第一种是采用import/from来引入,这种方式适合图片和所有模块,也是最符合规范,利用tree-shrink的。
2. 第二种是直接把图片提前压缩处理后放在public文件下,就可以通过根路径/xxx.png来访问到了。
3. 第三种使用vite提供的import.meta.glob()方法,但该方法返回的是异步的,适合配置懒加载动态路由。

这里我采用的是第二种方式,把boy.png放在public目录下,require引入改成固定字符串 '/boy.png' ,这样打包时就不用对图片做处理了,可以提高打包速度。

全局搜索一下require,把使用require引入图片的地方都改成绝对路径或者import/from引入。

修改后再次重启项目,这次修改后命令后依然没有报错,打开浏览器后页面出现了第六个报错

5.6 第六个报错, Failed to resolve import ‘../xxx.png’.

根据提示信息找到对应的文件,能清晰的看到报错原因,是因为页面里面使用img标签,src使用的相对路径

<img src="../../../static/userContent.png"">

static目录已经被public目录替换掉了,所以对于图片我们还是直接压缩处理后放的public文件夹,项目内使用绝对路径来引入,处理完后变成了

<img src="/userContent.png"">

其他页面有类似情况的可以用同样的方式来处理。

修改后再次重启项目,这次修改后命令后依然没有报错,打开浏览器后页面可以正常显示登录页面了,但是登录后页面又出现了第7个报错。

5.7 第七个报错, require引入动态异步组件问题.

看报错信息,这次和路由有关系,找到对应文件cache.js,发现页面使用require来引入动态路由组件了。

解决方案,采用vite提供的import.meta.glob方法,写法如下

const modules = import.meta.glob('./../../pages/**/*.vue');
function getBaseRouterWrap(item,PathMap,parentItem) {
  return {
    path: item.url,
    name: item.routerName,
    component: modules[`./../../${PathMap[item.routerName]}.vue`],
    meta: {
      parentName:parentItem ? parentItem.routerName : null,
      title: item.name,
      tag: true,
      icon: item.ico || '',
      needLogin: true,
      count: item.count,
      show: item.show
    }
  }
}

看一下const modules = import.meta.glob('./../../pages/**/**.vue');的返回结果

import.meta.glob方法会把路径下所有查询到的文件以路径为key,异步返回内容为value放到一个对象里面,使用的时候根据对于的文件名称引入,内容是 () => import('xxx.vue') 异步引入的,正好符合vue-router组件懒加载规则。

修改后再次重启项目,这次修改后命令后依然没有报错,打开浏览器后页面可以正常登录跳转页面了,但是跳转后页面又出现了第8个报错。

5.8 第八个报错, ‘xxx.less’ wasn't found..

发现还是因为路径的问题,有两种解决方式:

1. 第一种将所有的~@/改成我们已经配置到别名@/。
2. 新配置一个别名让~@/也指向src目录。

这里采用的第二种方式,比较简单,只改一个地方就好了。

修改后再次重启项目,这次修改后命令后依然没有报错,打开浏览器后页面可以正常跳转了,切换多个页面都没有发现问题,测试一下热更新速度,在某个页面进行测试的时候发现,每次热更新状态都会重置,查看文件加载情况,发现又重新引入了main.js。

5.9 第九个报错, 热更新后触发重新实例化vue流程

在查找原因后,发现在main.js里面定义了退出登录的方法,而在触发热更新的页面从main.js里面引入了该方法,所以每次触发热更新,重新请求该页面 .vue文件时,也会触发重新加载main.js,从而引起该问题的产生,解决方法,把main.js暴露的方法单独抽离到其他js文件里面,抽离后再测试就没问题了。

至此,开发阶段出现的报错就都已经解决了。

六. 项目打包阶段处理

6.1 处理项目兼容低版本浏览器

vite打包后默认目标浏览器是指向现带浏览器,就是支持es module的浏览器,可是在很多时候我们需要兼容低版本的浏览器,vite官方为我们提供了开箱即用的插件@vitejs/plugin-legacy

先安装插件

npm install @vitejs/plugin-legacy -D

vite.config.jsplguins插件里面添加

import legacy from '@vitejs/plugin-legacy'legacy({
  targets: ['ie >= 9'],
  additionalLegacyPolyfills: ['regenerator-runtime/runtime'],
})

targets要根据自己项目情况来配置,插件还有很多其他的配置,可以看插件文档

6.2 处理css加前缀问题

原项目配置了pocss-loader来打包时自动加css前缀来兼容低版本浏览器,所以迁移到vite后也需要处理下,在vite中可以采用autoprefixer来实现,安装:

npm install autoprefixer -D

vite.config.jscss.postcss.plugins里面添加autoprefixer插件,由于该插件默认只支持common.js,所以要用require引入,后面配置要支持的目标浏览器。

css: {
    postcss: {
      plugins: [
        require('autoprefixer')({
          overrideBrowserslist: ['Android 4.1', 'iOS 7.1', 'Chrome > 31', 'ff > 31', 'ie >= 9', '> 1%'],
          grid: true,
        }),
      ]
    }
  }

配置完成后,再次打包后,就可以看到css已经加上了前缀。

七. 总结

上面的完成后,打包就也没问题了,就可以连接项目git或者覆盖复制到原项目中,提交到测试环境开始构建了。

现在vite的生态也趋于变的成熟,已经可以满足于大部分业务场景了,不过在迁移前还是尽量要做下调研,看目前vite生态是否满足本项目中一些定制化或者特殊的场景,比如微前端electron支持等。

本文记录了在本项目迁移过程中遇到的问题,遇到的问题肯定只是webpack迁移vite很小的一部分问题,以后在迁移其他项目时候遇到新的问题也会补充进来。

本次迁移最终vite配置

import { defineConfig } from "vite";
import { createVuePlugin } from "vite-plugin-vue2";
import CompressionWebpackPlugin from 'vite-plugin-compression'
import legacy from '@vitejs/plugin-legacy'

export default defineConfig({
  plugins: [
    /** 支持vue2 **/
    createVuePlugin(),
    /** gzip压缩 **/
    CompressionWebpackPlugin({
      algorithm: 'gzip',
      threshold: 10240 //只有大小大于该值的资源会被处理。默认值是 10k
    }),
    /** 配置浏览器兼容 **/
    legacy({
      targets: ['ie >= 9'],
      additionalLegacyPolyfills: ['regenerator-runtime/runtime']
    })
  ],
  build: {
    chunkSizeWarningLimit: 2000,
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true
      }
    },
    brotliSize: false,
  },
  /** 为项目注入环境变量 **/
  define: {
    'process.env.BASE_ENV': JSON.stringify(process.env.BASE_ENV)
  },
  resolve: {
    /** 引入文件未带后缀时,依次查找数组里面配置的后缀文件 **/
    extensions: [".vue", ".js", ".json"],
    /** 配置alias别名 **/
    alias: [
      {
        find: '@/',
        replacement: '/src/'
      }
    ],
  },
  /** css自动加前缀 **/
  css: {
    postcss: {
      plugins: [
        require('autoprefixer')({
          overrideBrowserslist: ['Android 4.1', 'iOS 7.1', 'Chrome > 31', 'ff > 31', 'ie >= 9', '> 1%'],
          grid: true,
        }),
      ]
    }
  },
  server: {
    host: "0.0.0.0",
  },
});

参考资料:

vite官网:vitejs.cn