Vue2 工程优化实践 (webpack5 vite)

1,094 阅读4分钟

背景介绍

工程架构较老,随着项目越来越大,工程本地冷热启动、打包时间都较久,影响了开发的效率。
仅做了工程配置方面的优化,避免伤筋动骨的 Vue 版本升级,达到了较为满意的效果。

成果展示

打包速度优化

  • 旧 (182秒)

    • image.png
  • 新 (22秒)

    • image.png

本地启动( yarn serve )速度优化

  • 旧 无缓存启动 (65秒)
    • image.png
  • 旧 有缓存启动 (25.78秒)
    • image.png
  • 新 无缓存启动 (1.11秒)
    • image.png
  • 新 有缓存启动 (0.81秒)
    • Vite 的启动时间和是否有缓存关系不大 会有波动 都在 1s 左右

    • image.png

本地热更新速度优化

  • 旧 (4.04秒)
    • 如果之前做过类似的代码改动 命中缓存的话 可以降低到 2s 左右

    • image.png
  • 新 (≈0秒)
    • image.png

具体经历了哪些步骤?

1. 删除冗余的页面、路由、组件、依赖包

工程的完整路由一般存储在服务器中,拉来完整的,和 views 页面目录下进行一一对比,多的列出来,每一个都把文件所有涉及到的路由、组件、依赖包等拎出来,确定是否有其他页面引用到的,没有就全部剔除,有被其他引用到就加入待定名单,待全部冗余页面剔除完毕再看待定名单有没有多的可剔除的内容。

每处理一个页面,就提交一次代码,这样如果发现出现了问题,可以很方便地回滚到上一次提交。

2. webpack 从 4.x 升级到 5.x

webpack 5 不论是编译还是打包速度都有了大幅度的提升

vue-cli 从 4.x 升级到 5.x
webpack 亦随之从 4.x 升级到了 5.x

具体步骤

参考:juejin.cn/post/708905…

卸载旧版本 vue-cli
npm uninstall @vue/cli -g
安装最新版本 vue-cli
npm install -g @vue/cli@latest
进入到项目升级vue-cli
cd your-project 
vue upgrade --next
后续细节处理

会有很多关于 vue-loader 等的报错
很简单
抄 vue-cli 的默认 webpack 配置,问题定能迎刃而解 路径:node_modules/@vue/cli-service/lib/config/base.js

ps: 有问题请留言

原理

  • webpack4 根据源代码生成 chunkhash,注释换行的改动就会导致 chunkhash 改变,webpack5 根据源码的具体内容生成 chunkhash,不考虑源码内的注释换行等,chunkhash 不易改变,更容易命中缓存。
  • webpack4 的时候 chunkId 和 moduleId 都是自增的,增减模块都会导致缓存不命中,webpack5 优化了 chunkId 和 moduleId 的生成算法
  • 新增了硬盘缓存

3. 小的工程配置优化项

alias 加上 noParse

避免解析不需要的文件

module: {
  noParse: /^(vue|vue-router|vuex|vuex-router-sync|lodash|echarts|axios|view-design)$/,
}

配置 cache-loader

// vue.config.js
chainWebpack(config) {
  config.module
      .rule('vue')
        .test(/\.vue$/)
        .use('cache-loader')
          .loader(require.resolve('cache-loader'))
          .end()
        .use('vue-loader')
          .loader(require.resolve('@vue/vue-loader-v15'))
          .options(Object.assign({
            compilerOptions: {
              whitespace: 'condense'
            }
          }, {}))
          .end()
        .end();
}

配置 cdn

// vue.config.js
let cdn = {
  css: ['https://cdn01.xxx.com/iview.css'],
  js: [
    // vue must at first!
    'https://cdn01.xxx.com/vue.min.js',
    'https://cdn01.xxx.com/vuex.min.js',
    // etc...
  ],
};


chainWebpack(config) {
    config.plugin('html').tap((args) => {
      args[0].cdn = cdn;
      return args;
    });
    config.plugin('vue-loader')
      .use(require('@vue/vue-loader-v15').VueLoaderPlugin);
}

4. 开发环境迁移至 vite

  • 保证了打包的稳定性

  • 优化热更新至几乎 0秒

  • 优化本地启动至十秒内

原理

参考资料:www.lcs.show/blog/common…

  • 现代浏览器原生支持 ES6 的能力

  • 预构建

    • 浏览器无法识别commonjs的库,将依赖包以 es module 规范导出
  • 速度快

    • 依赖不变不执行

    • esbuild (速度极快)

      • Go 语言优势 (编译型语言)
      • 多进程

实践

  • 使用官方插件 vite-plugin-vue2

    • 将 Vue 2.6 升级至 2.7 (Vite 官方 vue2 插件从 Vue 2.7 开始支持)
  • 安装工程依赖

    • npm i -D vite vite-plugin-commonjs @vitejs/plugin-vue2
  • 在 package.json 添加 vite 启动指令

{
  "scripts": {
    "vite": "NODE_ENV=development vite"
  }
}
  • 创建 vite.config.js文件于项目根目录

    • 将 src 下不符合 esModule 规范的代码转换成符合的

      • vite-plugin-commonjs
    • 处理 process 在 Vite 工程中无法识别的问题

export default defineConfig(({ mode }) => {
  const envPrefix = ['VUE'];
  const env = loadEnv(mode, process.cwd(), envPrefix);
  const define = {
    'process.env.NODE_ENV': '"development"',
    'VUE_APP_ENV': '"development"',
    'process.env.VUE_APP_BATCH_DELIVER_TEMPLATE': '"https://xxx.oss-cn-shanghai.aliyuncs.com/prod/template/XLS/xxx.xlsx"'
  };

  for (const [key, value] of Object.entries(env)) {
    define[`process.env.${key}`] = `"${value}"`;
  }
  return {
    define,
  };
}
  • 处理动态引用在 Vite 工程支持不友好的问题
import dynamicImport from 'vite-plugin-dynamic-import';

plugins: [
  dynamicImport(),
],
  • 处理本地服务器代理
server: {
  open: true,
  port: config.port,
  proxy: {
    '/store': {
      target: 'http://dev.xxx.net', // 测试环境
      changeOrigin: true,
      logs: console,
      secure: false,
    },
  },
}
  • 综合如下
import vue from '@vitejs/plugin-vue2';
import { defineConfig, loadEnv } from 'vite';
import config from './src/config/index';
import commonjs from 'vite-plugin-commonjs';
import dynamicImport from 'vite-plugin-dynamic-import';
const path = require('path');

const resolve = (dir) => {
  return path.join(__dirname, dir);
};

export default defineConfig(({ mode }) => {
  const envPrefix = ['VUE'];
  const env = loadEnv(mode, process.cwd(), envPrefix);
  const define = {
    'process.env.NODE_ENV': '"development"',
    'VUE_APP_ENV': '"development"',
    'process.env.VUE_APP_BATCH_DELIVER_TEMPLATE': '"https://xxx.oss-cn-shanghai.aliyuncs.com/prod/template/XLS/xxx.xlsx"'
  };

  for (const [key, value] of Object.entries(env)) {
    define[`process.env.${key}`] = `"${value}"`;
  }
  return {
    define,
    root: './',
    publicDir: 'public',
    base: './',
    mode: 'development',
    plugins: [
      vue(),
      commonjs(),
      dynamicImport(),
    ],
    resolve: {
      alias: {
        '@': resolve('src'),
      },
      extensions: [
        '.vue',
        '.js',
      ],
    },
    css: {
      preprocessorOptions: {
        scss: {
          // 两种方式都可以
          additionalData: '@import "@/styles/common.scss";'
        },
        less: {
          javascriptEnabled: true,
        },
      }
    },
    server: {
      open: true,
      port: config.port,
      proxy: {
        '/store': {
          target: 'http://dev-gateway.xxx.net', // 测试环境
          changeOrigin: true,
          logs: console,
          secure: false,
        },
      },
    }
  };
});
  • 创建 index.html 于项目根目录

    • public/index.html文件的

      • 剔除 webpack特有的转义语法

      • html 文件中引用的文件目录记得相应更新

      • 在 index.html 文件中引入 src/main.js 文件

        • <script type="module" src="./src/main.js"></script>
    • <!DOCTYPE html>
      <html lang="en">
        <head>
          <meta charset="utf-8" />
          <meta http-equiv="X-UA-Compatible" content="IE=edge" />
          <meta name="viewport" content="width=device-width">
          <link rel="icon" href="./public/logo.ico" type="image/x-icon" />
          <title>store</title>
          <meta name="keywords" content="keywords" />
          <meta name="description" content="description" />
        </head>
        <body>
          <script type="module" src="./src/main.js"></script>
          <script src="./public/config.js"></script>
          <noscript>
            <strong
              >We're sorry doesn't work properly without JavaScript enabled. Please
              enable it to continue.</strong
            >
          </noscript>
          <div id="app">
            <div class="page-loading-wrap">
              <div class="half-circle-spinner">
                <div class="circle circle-1"></div>
                <div class="circle circle-2"></div>
              </div>
              <h4 style="margin-top: 20px">正在加载资源...</h4>
            </div>
          </div>
        </body>
      </html>
      

接下来就是细节不兼容处理

  • 旧版本 postcss不兼容,升级postcss版本

    • {
        "devDependencies": {
          "postcss": "^8.4.21"
        }
      }
      

结语

vite 用于本地开发真的很香,可以大大提升开发效率。 仅用于本地开发的话,还可以避免从 webpack 迁移到 rollup 打包带来的风险。 一开始配置遇到编译/运行错误,查下错误原因解决即可,虽然工程很大,但是改起来非常顺畅。(一般一小时内解决,比想象中快很多 ;)