艺灵设计

全部文章
×

Vue2后台项目改造成Vite2后的优化后续

作者:艺灵设计 - 来源:http://www.yilingsj.com - 发布时间:2022-08-09 15:26:09 - 阅: - 评:0 - 积分:0

摘要:上次咱们为了把Vue2项目改造成Vite2,踩的坑真是不要不要的!但改造后开发阶段的启动速度在5秒内秒开,真是太香了!在文章末尾,还有几个小问题没有解决,于是就有了此文。

5月份时,艺灵写了一篇关于Vite2的笔记→→《记一次成功把Vue2后台项目改造成Vite2的踩坑经历》,今天来接着写写后续的事情。

一、回顾上节报错问题与解决方案

1.1、ES6展开运算符导致的报错

关于上文中的“5.2、大家好才是真的好!”这里,有必要做下更正。当时出现报错的原因是因为我写了一行代码导致。感谢@徐宗纬(大大)发现了这个问题。具体代码如下↓↓↓

// 使用ES6展开运算符导致在cmd和vscode中启动报错
process.env = { ...process.env, ...loadEnv(mode, process.cwd()) } // 就是这行代码,在Git Bash Here中成功运行,在Cmd或Vs code中运行则失败

写这行代码一是因为项目有多环境配置,二是我要动态设置每个环境中的打包目录(build.outDir)来配合打包工具进行一键部署操作。

在这里使用ES6展开运算符竟然会失败,我有点儿不明白。

1.2、赋值给新变量则成功

当我把=左侧的process.env换成别的变量时则成功,这......

// 使用ES6展开运算符赋值新变量成功
const processEnv = { ...process.env, ...loadEnv(mode, process.cwd()) } // 现在,在Git Bash Here、Cmd和Vs code中运行均成功
// 下方代码中出现 process.env 的地方全换成 processEnv 即可。

虽然上面的方法也解决了问题,但我还是想继续使用原汁原味的process.env

1.3、Object.assign()合并失败

灵机一动,我想到了Object.assign(),遗憾的是同样是失败!

// Object.assign()
Object.assign(process.env, ...loadEnv(mode, process.cwd())) // 这样写也失败

展开运算符赋值和合并的操作都失败了,那还有没有别的方法呢?

1.4、Object.keys()遍历赋值成功

思索片刻后,我想到了遍历赋值的方法。

// Object.keys()遍历赋值,结果成功
// 遍历当前环境中自定义的变量
Object.keys(loadEnv(mode, process.cwd())).filter((key) => {
  // 循环把变量挂给 process.env
  process.env[key] = loadEnv(mode, process.cwd())[key] // 在Git Bash Here、Cmd和Vs code中运行均成功
})

修改后在cmd中跑了下项目,启动成功!

此时,可运行的相关代码片段如下↓↓↓

// 代码片段如下 ↓↓↓
import { defineConfig, loadEnv } from 'vite'
export default ({ mode }) => {
  // 遍历挂载当前环境的配置文件到process.env上,后面可在process.env中访问.env.xxx里面的内容
  Object.keys(loadEnv(mode, process.cwd())).filter((key) => {
    process.env[key] = loadEnv(mode, process.cwd())[key]
  })
  return defineConfig({
    ...... // 省略一些配置
    server: {
      open: true, // 自动打开浏览器
    },
    build: {
      outDir: process.env.VITE_APP_OUTPUTDIR, // 设置当前环境对应的打包目录
    }
  })
}

1.5、还可通过安装 dotenv 插件来解决此问题

关于获取多环境配置这个问题,除了前面的loadEnv外,网上资源提到还可通过安装三方插件dotenv来读取自定义配置。

// 安装dotenv
npm install dotenv --save-dev
// 读取多环境配置变量
import { defineConfig } from 'vite'
export default ({ mode }) => {
  require('dotenv').config({ path: `./.env.${mode}` }) // 可在process.env中访问.env.xxx里面的内容
  return defineConfig({
    ...... // 省略一些配置
    server: {
      open: true, // 自动打开浏览器
    },
    build: {
      outDir: process.env.VITE_APP_OUTPUTDIR, // 设置当前环境对应的打包目录
    }
  })
}

1.6、小结

回头再看看这个问题,明明有更简单的方法,当时我为什么不知道变通呢?快被自己蠢哭了😭😭😭

// 解构loadEnv即可
import { defineConfig, loadEnv } from 'vite'
export default ({ mode }) => {
  const { VITE_APP_OUTPUTDIR } = loadEnv(mode, process.cwd()) // 获取当前环境中定义的打包目录
  console.log('VITE_APP_OUTPUTDIR=', VITE_APP_OUTPUTDIR) // 输出 dist 或 dist/test 或 dist/pre 等,具体输出内容跟配置有关
  return defineConfig({
    ...... // 省略一些配置
    server: {
      open: true, // 自动打开浏览器
    },
    build: {
      outDir: VITE_APP_OUTPUTDIR, // 设置当前环境对应的打包目录
    }
  })
}

二、项目打包目录细分化

上一篇中,我们已经把Vue2后台项目改造成Vite版,打包后的默认目录结构如下↓↓↓

// vite默认打包时静态资源都在static目录中↓↓↓
static // 资源目录,里面包含有js、css、图片、字体文件等
index.html // 首页
favicon.ico // 网站图标

配图:css-js-图片字体资源都在一个文件夹.png css-js-图片字体资源都在一个文件夹

那如何把jscss目录独立开来呢?

2.1、通过build.rollupOptions来拆分css、js目录

通过配置build.rollupOptions即可解决此问题。这里要特别注意assetFileNames,相关代码见下方。

// 配置build.rollupOptions来拆分静态资源目录↓↓↓
import { defineConfig, loadEnv } from 'vite'
const moment = require('dayjs')
const formatTime = moment().format('YYYYMMDD_HHmmss')
export default ({ mode }) => {
  const { VITE_APP_OUTPUTDIR } = loadEnv(mode, process.cwd()) // 获取当前环境中定义的打包目录
  return defineConfig({
    ...... // 省略一些配置
    server: {
      open: true,
    },
    build: {
      target: 'esnext',
      assetsDir: 'static',
      outDir: VITE_APP_OUTPUTDIR, // 设置当前环境对应的打包目录
      assetsInlineLimit: 1024 * 10, // 10kb,小于此阈值的导入或引用资源将内联为 base64 编码
      rollupOptions: {
        output: {
          // 用于从入口点创建的块的打包输出格式[name]表示文件名
          entryFileNames: `js/[name].${formatTime}.js`, // 用时间戳代替hash,一看源码就知道代码是什么时候上传的,清晰明了,避免扯皮~
          // 用于命名代码拆分时创建的共享块的输出命名
          chunkFileNames: `js/[name].${formatTime}.js`,
          // 用于输出静态资源的命名,把css剥离出来,剩下资源还在static中
          assetFileNames: `css/[name].${formatTime}.css`, // 或者用下面的写法↓↓↓
          // 静态资源以扩展名来拆分目录([ext]表示文件扩展名)
          // assetFileNames: `[ext]/[name]-${formatTime}.[ext]`, // 打包后的目录中可能会出现png、jpg、svg、ttf、gif等目录。
        },
      },
    },
  })
}

配置后再打包,目录结构如下↓↓↓

// 打包时拆分css和js目录↓↓↓
css // css样式文件
js // js文件
static // 资源目录,里面包含图片、字体等
index.html // 首页
favicon.ico // 网站图标

现在,cssjs已经成功的从static目录中抽离出来,跟static同级了。

三、项目打包时长和代码体积的分析

关于这个问题,整个测试过程还挺折腾人的。

除了项目本身庞大外,还跟一些配置有关。现将一些配置和每次打包结果统计如下,有兴趣的看官可以瞅瞅。

如果想直接看结论,可拉到3.6.3、小结那里。

3.1、legacy + gzip + manualChunks + terser + terserOptions

小标题中的legacy是指@vitejs/plugin-legacy插件,目的是为了兼容传统浏览器。
gzip是指vite-plugin-compression插件,可以将超过限制的代码压缩成.gz后缀。此配置需要服务器支持,否则发挥不了价值还容易引起报错。
manualChunks是指build.rollupOptions.output.manualChunks的配置,可以拆分三方依赖库,猛戳→→→访问文档
terser + terserOptions用来清除注释和日志。

这套配置包含了兼容低版本浏览器 + gzip压缩 + 移除注释和console,也是我平时使用的配置。具体代码如下:

// 配置legacy + gzip + manualChunks + terser + terserOptions ↓↓↓
import { defineConfig, loadEnv } from 'vite' // vite版本是2.9.14
import legacy from '@vitejs/plugin-legacy' // 兼容传统浏览器的代码
import viteCompression from 'vite-plugin-compression' // gzip压缩 https://github.com/vbenjs/vite-plugin-compression/blob/main/README.zh_CN.md
const moment = require('dayjs')
const formatTime = moment().format('YYYYMMDD_HHmmss')
export default ({ mode }) => {
  const { VITE_APP_OUTPUTDIR } = loadEnv(mode, process.cwd())
  return defineConfig({
    ...... // 省略一些配置
    plugins: [
      ...... // 省略一些别的插件
      // 兼容传统浏览器的代码
      legacy({
        targets: ['defaults', 'ie >= 11', 'chrome 52'],
        additionalLegacyPolyfills: ['regenerator-runtime/runtime'],
      }),
      // 超过100kb则进行gzip压缩
      viteCompression({
        verbose: false,
        threshold: 1024 * 100, // 单位b
      }),
    ],
    server: {
      open: true,
    },
    build: {
      target: 'esnext', // 如果 build.minify 选项为 'terser', 'esnext' 将会强制降级为 'es2019'
      assetsDir: 'static',
      outDir: VITE_APP_OUTPUTDIR,
      assetsInlineLimit: 1024 * 10,
      rollupOptions: {
        output: {
          entryFileNames: `js/[name].${formatTime}.js`,
          chunkFileNames: `js/[name].${formatTime}.js`,
          assetFileNames: `css/[name].${formatTime}.css`,
          // 拆分三方依赖(拆分成若干小文件),并自动插入到index.html中
          manualChunks(id) {
            if (id.includes('node_modules')) {
              return id.toString().split('node_modules/')[1].split('/')[0].toString()
            }
          },
        },
      },
    },
    minify: 'terser', // 这一行开启后,下面的 terserOptions 才能生效
    terserOptions: {
      compress: {
        drop_console: true, // 移除console
      },
      output: {
        comments: true, // 去掉注释内容
      },
    },
  })
}

打包后手动统计信息如下↓↓↓
耗时 203215 ms,
整个文件夹体积 37.3MB (css 1.5MB + js 23.7MB + static 12.1MB)
将文件夹打包成压缩包后的体积 20.8MB

此时查看index.html页面源码,会发现多出很多jscss文件。

这些自动插入的cssjs文件,正是配置了manualChunks(id)才实现的。如下图:配置manualChunks(id)后会在index.html中自动加载相关的js和css.png 配置manualChunks(id)后会在index中自动加载相关的js和css

3.2、legacy + manualChunks + terser + terserOptions

前面说了,gzip需要服务器支持,我们先去掉gzip,再看下打包后的体积吧,应该会有所减小。

// 配置legacy + manualChunks + terser + terserOptions ↓↓↓
import { defineConfig, loadEnv } from 'vite' // vite版本是2.9.14
import legacy from '@vitejs/plugin-legacy' // 兼容传统浏览器的代码
const moment = require('dayjs')
const formatTime = moment().format('YYYYMMDD_HHmmss')
export default ({ mode }) => {
  const { VITE_APP_OUTPUTDIR } = loadEnv(mode, process.cwd())
  return defineConfig({
    ...... // 省略一些配置
    plugins: [
      ...... // 省略一些别的插件
      // 兼容传统浏览器的代码
      legacy({
        targets: ['defaults', 'ie >= 11', 'chrome 52'],
        additionalLegacyPolyfills: ['regenerator-runtime/runtime'],
      }),
    ],
    server: {
      open: true,
    },
    build: {
      target: 'esnext',
      assetsDir: 'static',
      outDir: VITE_APP_OUTPUTDIR,
      assetsInlineLimit: 1024 * 10,
      rollupOptions: {
        output: {
          entryFileNames: `js/[name].${formatTime}.js`,
          chunkFileNames: `js/[name].${formatTime}.js`,
          assetFileNames: `css/[name].${formatTime}.css`,
          // 拆分三方依赖(拆分成若干小文件),并自动插入到index.html中
          manualChunks(id) {
            if (id.includes('node_modules')) {
              return id.toString().split('node_modules/')[1].split('/')[0].toString()
            }
          },
        },
      },
    },
    minify: 'terser', // 这一行开启后,下面的 terserOptions 才能生效
    terserOptions: {
      compress: {
        drop_console: true, // 移除console
      },
      output: {
        comments: true, // 去掉注释内容
      },
    },
  })
}

打包后手动统计信息如下↓↓↓
耗时 199867 ms,
整个文件夹体积 34.5MB (css 1.39MB + js 21.0MB + static 12.0MB)
将文件夹打包成压缩包后的体积 17.5MB

通过对比3.13.2可以看到,打包耗时相差3s左右,项目体积减少了2.8MB

3.3、只配置 legacy + manualChunks

现在我们保留下注释和日志,再看下打包后的时间和体积吧。

// 只配置 legacy + manualChunks ↓↓↓
import { defineConfig, loadEnv } from 'vite' // vite版本是2.9.14
import legacy from '@vitejs/plugin-legacy' // 兼容传统浏览器的代码
const moment = require('dayjs')
const formatTime = moment().format('YYYYMMDD_HHmmss')
export default ({ mode }) => {
  const { VITE_APP_OUTPUTDIR } = loadEnv(mode, process.cwd())
  return defineConfig({
    ...... // 省略一些配置
    plugins: [
      ...... // 省略一些别的插件
      // 兼容传统浏览器的代码
      legacy({
        targets: ['defaults', 'ie >= 11', 'chrome 52'],
        additionalLegacyPolyfills: ['regenerator-runtime/runtime'],
      }),
    ],
    server: {
      open: true,
    },
    build: {
      target: 'esnext',
      assetsDir: 'static',
      outDir: VITE_APP_OUTPUTDIR,
      assetsInlineLimit: 1024 * 10,
      rollupOptions: {
        output: {
          entryFileNames: `js/[name].${formatTime}.js`,
          chunkFileNames: `js/[name].${formatTime}.js`,
          assetFileNames: `css/[name].${formatTime}.css`,
          // 拆分三方依赖,并自动插入到index.html中
          manualChunks(id) {
            if (id.includes('node_modules')) {
              return id.toString().split('node_modules/')[1].split('/')[0].toString()
            }
          },
        },
      },
    },
  })
}

打包后手动统计信息如下↓↓↓
耗时 185759 ms,
整个文件夹体积 32.6MB (css 1.39MB + js 19.1MB + static 12.0MB)
将文件夹打包成压缩包后的体积 17.1MB

通过对比3.23.3可以发现,去除配置terser + terserOptions后体积和打包时间竟然比优化前还少???

是不是很奇怪呀?vite官方文档中是这样说的:

如果 build.minify 选项为 'terser', target: 'esnext' 将会强制降级为 'es2019'。
默认为 'esbuild',它比 terser 快 20-40 倍,压缩率只差 1%-2%。Benchmarks 。
文档地址: https://vitejs.cn/config/#build-target

3.4、只配置 legacy

接下来,js大文件我们也不拆分成若干小模块了,再看看结果吧。

// 只配置legacy ↓↓↓
import { defineConfig, loadEnv } from 'vite' // vite版本是2.9.14
import legacy from '@vitejs/plugin-legacy' // 兼容传统浏览器的代码
const moment = require('dayjs')
const formatTime = moment().format('YYYYMMDD_HHmmss')
export default ({ mode }) => {
  const { VITE_APP_OUTPUTDIR } = loadEnv(mode, process.cwd())
  return defineConfig({
    ...... // 省略一些配置
    plugins: [
      ...... // 省略一些别的插件
      // 兼容传统浏览器的代码
      legacy({
        targets: ['defaults', 'ie >= 11', 'chrome 52'],
        additionalLegacyPolyfills: ['regenerator-runtime/runtime'],
      }),
    ],
    server: {
      open: true,
    },
    build: {
      target: 'esnext',
      assetsDir: 'static',
      outDir: VITE_APP_OUTPUTDIR,
      assetsInlineLimit: 1024 * 10,
      rollupOptions: {
        output: {
          entryFileNames: `js/[name].${formatTime}.js`,
          chunkFileNames: `js/[name].${formatTime}.js`,
          assetFileNames: `css/[name].${formatTime}.css`,
        },
      },
    },
  })
}

打包后手动统计信息如下↓↓↓
耗时 203578 ms,
整个文件夹体积 31.3MB (css 1.39MB + js 17.8MB + static 12.0MB)
将文件夹打包成压缩包后的体积 17.1MB

对比3.33.4可以发现,打包时间略有所上升,文件夹体积相差1.3MB,变化不大。

3.5、啥配置也不加,裸奔!

最后,浏览器兼容也不做了,放弃@vitejs/plugin-legacy这个插件,再来看下打包后的数据吧。

// 啥配置也不加,裸奔! ↓↓↓
import { defineConfig, loadEnv } from 'vite' // vite版本是2.9.14
const moment = require('dayjs')
const formatTime = moment().format('YYYYMMDD_HHmmss')
export default ({ mode }) => {
  const { VITE_APP_OUTPUTDIR } = loadEnv(mode, process.cwd())
  return defineConfig({
    ...... // 省略一些配置
    plugins: [
      ...... // 省略一些别的插件
    ],
    server: {
      open: true,
    },
    build: {
      target: 'esnext',
      assetsDir: 'static',
      outDir: VITE_APP_OUTPUTDIR,
      assetsInlineLimit: 1024 * 10,
      rollupOptions: {
        output: {
          entryFileNames: `js/[name].${formatTime}.js`,
          chunkFileNames: `js/[name].${formatTime}.js`,
          assetFileNames: `css/[name].${formatTime}.css`,
        },
      },
    },
  })
}

打包后手动统计信息如下↓↓↓
耗时 81335 ms,
整个文件夹体积 21.3MB (css 13.2MB + js 8.01MB + static 无此目录)
将文件夹打包成压缩包后的体积 14.5MB

注意看上方标红的数字哈。

天呐!打包时间竟然大大缩减到了80几秒,项目总体积从一开始的37.3优化到了21.3,节省了16MB的空间!

这。。。也太神奇了吧!

嗯?不对呀!前面几次打包的css一直都控制在1.5MB内,怎么现在突然超过10MB,发生了什么事?

还有js,前面几次打包都是17+MB,现在突然降到了10MB以下,少去的这近10MB莫非是兼容的文件??

静态资源目录static也不见了,那图片、字体去哪儿了?

莫非,css中夹杂着static里面的内容???

......

关于上面的疑问,暂时先留个悬念,在3.6、都是配置的坑中会解答。

对比3.43.5可以发现,文件体积相差10MB!

对比几轮测试数据,得出的结论是:影响本项目打包时间和体积的主要因素是@vitejs/plugin-legacy,其次是vite-plugin-compression

3.6、都是配置的坑

cmd或右键git进入打包后的目录dist/dev,输入http-server即可启动本地web服务。接着在浏览器中输入终端窗口中出现的ip地址即可访问项目,例如:127.0.0.1:8080

有必要说明下,http-server是需要手动安装的,可以安装到全局,用起来很方便的。

// 安装http-server来启动本地web服务
npm install http-server -g // 安装
http-server // 在目标目录中,打开终端并执行此命令,即可启动web服务

chrome浏览器(版本 101.0.4951.54 正式版本)登录后台,随便点了几个页面,一切正常。

360极速浏览器(版本:13.5.2022.0)登录后台,首页布局正常。再点几个页面,布局乱了,部分图标也不显示![:尴尬][:尴尬][:尴尬]

360安全浏览器(版本:13.1.6110.0)的极速模式访问后台,结果同360极速浏览器。用IE兼容模式,页面空白,控制台无报错。

切换到360极速浏览器,打开控制台审查一下对应的元素,发现几个有意思的现象:
1、大图片的后缀变成了.css,关键是还能正常显示!小图标仍为base64
2、svg格式的小图标也变成了.css后缀,无法显示。

如下图:不做浏览器兼容时大图片后缀变成了css.png 不做浏览器兼容时大图片后缀变成了css

这种诡异的打包现象让我捉摸不透,在网上搜索了一圈也没有找到原因。

3.6.1、小坑:output.assetFileNames

看了看上面的配置,图片和字段资源都被打包到了css目录中且连后缀都发生了变化,莫非与output.assetFileNames有关?

随即我注释掉了output.assetFileNames的配置。再次打包,发现static目录出现了,里面夹杂着csspngsvgttf等。

刷新360极速浏览器,图标正常显示,大图片也恢复正常了。布局错乱的问题仍然存在。

审查元素发现原来是一处css代码出现了异常。如下图:布局错乱.png 布局错乱

在项目中搜索了下inset:,结果为0。也就是说没有匹配到,这就奇怪了。我又换了个搜索条件,改为搜索.rightmenu类,结果找到了。但奇怪的是代码中确实没有出现inset:,代码是这样写的。

// 项目中写法正常,打包后变成了inset
.rightmenu {
  transition: width 0.28s;
  left: $newSideBarWidth;
  background-color: $menuBg;
  height: 100%;
  position: absolute;
  font-size: 0px;
  top: 0;
  bottom: 0;
  right: 0;
  z-index: 1001;
  // overflow: hidden;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
  padding-top: 50px;
}

尝试注释掉top: 0;bottom: 0;,再次打包。刷新360极速浏览器,代码恢复正常。

上述方案又是去掉配置又是删除代码的,虽然解决了问题,但明显是有问题的。很有可能在某些没有点到的页面仍出现异常。

话又说回来,从3.1到3.4,几次打包均正常,只有3.5放弃浏览器兼容后才出现了异常。

此处省略若干折腾内容......

3.6.2、大坑:vite版本不能太高!

看了下项目中的package.json文件,显示的是vite:"^2.4.4",但在运行时变成了2.9.14版本。也不知道是在哪次npm install时自动升级了版本。

在项目根目录下重新安装vite,输入npm install vite@2.4.4 --save-dev后回车安装指定版本。安装成功后执行打包命令,打包后手动统计信息如下↓↓↓
耗时 72493 ms,
整个文件夹体积 21.0MB (css 1.4MB + js 7.68MB + static 11.9MB)
将文件夹打包成压缩包后的体积 14.4MB

久违的static目录再次出现,进去一看,pngsvgttf等文件都在。刷新360极速浏览器,inset不见了,页面布局正常。

在3.5上安装了几个不同版本的vite,例如:2.5.02.7.52.8.32.9.0,打包后均会出现3.6.1的问题。向下测试了2.3.8版本,打包后正常。

3.6.3、小结

所以目前的结论就是:在不做兼容的情况下,项目打包时间和体积都大大缩减,vite2.4.4以上版本是影响项目打包后异常的主要原因。

四、项目是否有必要做兼容性处理

通过前面一系列的对比可以看到,浏览器兼容性插件@vitejs/plugin-legacy会导致当前项目体积额外增加近10MB,同时打包时间也80秒左右上升到了200秒左右,对项目来说,影响还是蛮大的。

随手搜索了几个写有letconstawait、箭头函数=>对应的xxx-legacy.xxx.js文件,发现let被和const转换成了varawait也不见了。

也就是说,ES6ES7这些都做了兼容处理,至于其他方面的兼容性,暂时没做深入了解。

话说微软前段时间已经宣布放弃了IE11浏览器,也就意味着前端终于可以不再被兼容性所掉头发了!

但话又说回来,不考虑兼容性这一想法仍然需要很长一段时间来进行过渡。毕竟很多企业单位还用着老香的XP系统

所以说,做不做兼容,得根据项目受众来做结论。

再看看我们的用户,只会访问正式环境,对于开发、测试、预上线这3个环境都是我们自己人,所以我只需要给正式环境做下兼容不就好了嘛!?这样一来,虽然平时打包多,但都是非正式环境。正式环境一周才打包一次,一次等待个3分钟也能接受。反正现在是脚本一键打包并部署,我也不用时时看着啥时候打包完成。

4.1、判断打包环境,正式环境才做浏览器兼容处理

// 正式环境才做浏览器兼容处理 ↓↓↓
import { defineConfig, loadEnv } from 'vite' // 注意这里的vite版本是2.4.4
import { createVuePlugin } from 'vite-plugin-vue2' // Vue2支持,https://vitejs.cn/guide/features.html#vue
import { viteCommonjs } from '@originjs/vite-plugin-commonjs' // 让浏览器支持commonjs语法
import { cjs2esmVitePlugin } from 'cjs2esmodule' // 将 commonjs 转化为 es module
import html from 'vite-plugin-html' // 为index.html提供minify和基于EJS模板功能
const moment = require('dayjs')
const formatTime = moment().format('YYYYMMDD_HHmmss')
const htmlOption = {
  inject: {
    injectData: {
      title: 'xxx后台管理系统',
    },
  },
}
export default ({ mode }) => {
  const { VITE_APP_OUTPUTDIR } = loadEnv(mode, process.cwd())

  /* 如果是正式环境,则做浏览兼容性处理↓↓↓ */
  const plugins = []
  if (mode === 'production') {
    const legacyOption = {
      targets: ['defaults', 'ie >= 11', 'chrome 52'],
      additionalLegacyPolyfills: ['regenerator-runtime/runtime'],
    }
    // 兼容传统浏览器的代码
    plugins.push(legacy(legacyOption))
  }
  /* 如果是正式环境,则做浏览兼容性处理↑↑↑ */

  return defineConfig({
    ...... // 省略一些配置
    plugins: [
      ...... // 省略一些别的插件
      ...plugins,
      html(htmlOption),
      /* vue2项目需要用到↓↓↓ */
      cjs2esmVitePlugin(),
      createVuePlugin(),
      viteCommonjs(),
      /* vue2项目需要用到↑↑↑ */
    ],
    server: {
      open: true,
    },
    build: {
      target: 'esnext',
      assetsDir: 'static',
      outDir: VITE_APP_OUTPUTDIR,
      assetsInlineLimit: 1024 * 10,
      rollupOptions: {
        output: {
          entryFileNames: `js/[name].${formatTime}.js`,
          chunkFileNames: `js/[name].${formatTime}.js`,
          // 用于输出静态资源的命名,把css剥离出来,剩下资源还在static中
          assetFileNames: `css/[name].${formatTime}.css`, // `[ext]/[name]-${formatTime}.[ext]`
        },
      },
    },
  })
}

好了,平时再打包非正式环境代码到服务器上,基本上1分钟半左右就能听到音乐响起,跟之前200s相比,还是有很大的提升的哈。

4.2、提升用户体验

那万一有用户使用ie内核的浏览器访问我们的后台怎么办?

这个时候为了提升用户体验,我们可以对IE浏览器做单独处理。本来想用IE的专属条件注释来实现功能,结果发现在高版本上已经不支持了。网上找了串代码,测试通过。相关代码如下。

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  <meta name="referrer" content="origin-when-cross-origin" />
  <link rel="icon" href="/favicon.ico">
  <title>
    <%- title %>
  </title>

<!-- css样式设置 ↓↓↓ -->
<style>
    .ie {
      position: fixed;
      bottom: 0;
      left: 0;
      right: 0;
      line-height: 2;
      padding: 15px;
      background-color: rgba(0, 0, 0, .5);
      text-align: center;
      color: #fff;
      z-index: 999999999;
      font-weight: bolder;
    }
</style>
<!-- css样式设置 ↑↑↑ -->

</head>
<body>
  <div id="app"></div>

  <!-- js逻辑 ↓↓↓ -->
  <script>
      // 判断是否为ie浏览器
      function isIE() {
        if (!!window.ActiveXObject || "ActiveXObject" in window) {
          var div = document.createElement('div')
          div.innerText = '为了更好的体验,请使用非IE浏览器访问。例如:chrome浏览器'
          div.className = 'ie'
          document.body.appendChild(div)
        }
      }
      isIE()
  </script>
  <!-- js逻辑 ↑↑↑ -->

  <script type="module" src="/src/main.js"></script>
  <!-- built files will be auto injected -->
</body>
</html>

现在,用户使用IE浏览器访问项目,会在底部有一行文本进行提醒。是不是人性化了很多呀,哈哈!如图:使用ie内核浏览器访问时会在页面底部提醒更换浏览器

五、项目引入cdn后对打包时间和体积有所改善吗?

关于这个话题,之前我也挺好奇的,不过对比多次打包后的数据,我发现并不是很香。有点儿剧透了哈,还是先来看看如何配置cdn吧。

5.1、首先修改index.html

根据自己项目中使用的三方库来确实需要引入哪些cdn资源,版本的话可查看package.json中的dependenciesdevDependencies

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  <meta name="referrer" content="origin-when-cross-origin" />
  <link rel="icon" href="/favicon.ico">
  <title>
    <%- title %>
  </title>
</head>
<body>
  <div id="app"></div>
  <!-- 引入cdn,需要在vite.config.js中配置↓↓↓ -->
  <script src="https://unpkg.com/vue@2.5.9/dist/vue.min.js"></script>
  <script src="https://unpkg.com/vuex@3.0.1/dist/vuex.min.js"></script>
  <script src="https://unpkg.com/element-ui@2.13.2/lib/index.js"></script>
  <script src="https://unpkg.com/vue-router@3.0.1/dist/vue-router.min.js"></script>
  <script src="https://unpkg.com/lodash@4.17.21/lodash.min.js"></script>
  <script src="https://unpkg.com/echarts@5.1.2/dist/echarts.min.js"></script>
  <script src="https://unpkg.com/axios@0.18.1/dist/axios.min.js"></script>
  <!-- 引入cdn,需要在vite.config.js中配置↑↑↑ -->
  <script type="module" src="/src/main.js"></script>
  <!-- built files will be auto injected -->
</body>
</html>

5.2、接着修改 vite.config.js

需要注意rollupOptions.externalrollupOptions.plugins这两处的配置。

// 核心配置如下 ↓↓↓
import { defineConfig, loadEnv } from 'vite' // 注意这里的vite版本是2.4.4
import commonjs from 'rollup-plugin-commonjs'
// 文档:https://www.npmjs.com/package/rollup-plugin-external-globals
import externalGlobals from 'rollup-plugin-external-globals' // 将外部导入转换为全局变量
...... // 省略一些配置

/* 此处为新增↓↓↓ */
// 使用cdn的一些库
const externals = {
  vue: 'Vue', // 冒号左侧的为from右侧的内容,冒号右侧的为from左侧的内容。例如:import Vue from 'vue'
  'vue-router': 'VueRouter',
  axios: 'axios',
  vuex: 'Vuex',
  'element-ui': 'ELEMENT', // 注意这里,用ElementUI会报错。
  lodash: '_',
  echarts: 'echarts',
}
const external = Object.keys(externals)
/* 此处为新增↑↑↑ */

export default ({ mode }) => {
  const { VITE_APP_OUTPUTDIR } = loadEnv(mode, process.cwd())
  return defineConfig({
    ...... // 省略一些配置
    plugins: [
      ...... // 省略一些别的插件,无新增修改
    ],
    server: {
      open: true,
    },
    build: {
      target: 'esnext',
      assetsDir: 'static',
      outDir: VITE_APP_OUTPUTDIR,
      assetsInlineLimit: 1024 * 10,
      rollupOptions: {
        output: {
          entryFileNames: `js/[name].${formatTime}.js`,
          chunkFileNames: `js/[name].${formatTime}.js`,
          // 用于输出静态资源的命名,把css剥离出来,剩下资源还在static中
          assetFileNames: `css/[name].${formatTime}.css`, // `[ext]/[name]-${formatTime}.[ext]`
        },

        /* 此处为新增 ↓↓↓ */
        external: external,
        plugins: [commonjs(), externalGlobals(externals)],
        /* 此处为新增 ↑↑↑ */

      },
    },
  })
}

5.3、最后修改 main.js

最后要修改下导入element-ui的包名,其他地方无需修改。

// 修改element-ui ↓↓↓
import ELEMENT from 'element-ui'
Vue.use(ELEMENT, { locale })

再次打包,打包后手动统计信息如下↓↓↓
耗时 70218 ms,
整个文件夹体积 19.6MB (css 1.4MB + js 6.26MB + static 11.9MB)
将文件夹打包成压缩包后的体积 14.1MB。

如图:使用cdn引入三方库后,js体积下降到6MB多.png 使用cdn引入三方库后,js体积下降到6MB多

多次打包后的时间都相差不大,几秒的区别。整个文件夹体积的话,7个cdn加起来节省了1.4MB的空间。但网页加载还是会加载这1.4MB的资源的哈,只是不在自己服务器上哈。

使用cdn引入三方库后,这个一直都比较大的vendor.js从原来的2MB降到了900KB,说明前面配置的cdn还是有效果的。

5.4、项目无法启动了

进入dist/dev目录后用http-server启动一个本地web服务,访问页面并打开控制台,一切正常。

接着用npm run dev启动项目后,页面空白,此时有种不详的预感。赶紧按下F12,控制台果然有报错!

// 报错:Object.defineProperty(Vue.prototype, '$router' ↓↓↓
Uncaught TypeError: Cannot redefine property: $router
    at Function.defineProperty (<anonymous>)
    at Function.install (vue-router.esm.js:565:10)
    at St.e.use (vue.min.js:6:21850)
    at vue-router.esm.js:2668:14

网上提供两种解决方案,
一、删除掉index.html中的vue-router.js
二、卸载掉项目中的vue-router,相关命令→→→npm uninstall vue-router --save-dev

这里采用了方案一,刷新网页后接着有新的报错。

// 新报错:Cannot read properties of undefined (reading '_normalized') ↓↓↓
TypeError: Cannot read properties of undefined (reading '_normalized')
    at normalizeLocation (vue-router.esm.js:1295:12)
    at VueRouter2.resolve (vue-router.esm.js:2622:18)
    at a.render2 (vue-router.esm.js:423:22)
    at e._render (vue.min.js:6:64049)
    at a.r (vue.min.js:6:11462)
    at Uo.get (vue.min.js:6:59164)
    at new Uo (vue.min.js:6:59087)
    at Se (vue.min.js:6:11476)
    at Ot.$mount (vue.min.js:6:80197)
    at Ot.$mount (vue.min.js:6:86556)

vue.min.js:6 TypeError: Cannot read properties of undefined (reading 'dropdownSize')
    at a.data (element-ui.common.js:2643:27)
    at a.mergedDataFn (vue.runtime.esm.js:1219:51)
    at ze (vue.min.js:6:14158)
    at Ve (vue.min.js:6:13992)
    at Be (vue.min.js:6:13600)
    at e._init (vue.min.js:6:61372)
    at new a (vue.min.js:6:22165)
    at createComponentInstanceForVnode (vue.runtime.esm.js:3283:10)
    at init (vue.runtime.esm.js:3114:45)
    at u (vue.min.js:6:70638)

[Vue warn]: Error in mounted hook: "TypeError: Cannot set properties of undefined (setting 'popperElm')"
(found in <Root>)
......

这。。。。。。

太坑了吧,又是一连串的报错。

算了,不折腾了。毕竟改用cdn后优化效果也不明显,投入太多时间并不值得。

六、可视化分析插件rollup-plugin-visualizer

如果你想分析项目中哪些文件比较大以及为什么那么大,可以使用一些插件进行辅助分析。

Vue2项目中,通过webpack-bundle-analyzer插件可分析项目依赖关系,然后再进行相关优化。相关配置如下↓↓↓

6.1、Vue2项目的配置

// 在vue.config.js中进行配置 ↓↓↓
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin // 可视化分析包大小插件
const analyzerPort = 8000 + Math.ceil(Math.random() * 1000) // 随机端口
export default defineConfig({
  configureWebpack: {
    plugins: [
      new BundleAnalyzerPlugin({ analyzerPort: analyzerPort })
      ...... // 省略一些其他配置
    ]
  }
})

上述的插件适用于Vue2项目,对于Vite2项目需要安装对应的插件→→→ rollup-plugin-visualizer即可。

6.2、Vite2项目的配置

首先是通过npm install命令进行安装。

// npm install 安装插件↓↓↓
npm install rollup-plugin-visualizer --save-dev

安装完后,在vite.config.js中进行相关配置。

// 在vite.config.js中进行配置↓↓↓
import { visualizer } from 'rollup-plugin-visualizer' // 可视化分析包大小插件
// 文档:https://www.npmjs.com/package/rollup-plugin-visualizer
export default ({ mode }) => {
  return defineConfig({
    plugins: [
      visualizer({ open: true, template: 'treemap' }), // 仅在build时可用,开发环境无效
      ...... // 省略一些其他配置
    ]
  })
}

6.3、3种模板供选择

现在我们来体验一下吧。

在项目根目录下打开Cmd或右键Git,接着执行npm run build:dev打包开发环境。

大概等上1分钟左右,浏览器会自动打开一个名叫stats.html且非常花哨的页面。

该页面中由非常多的色块组成,有的色块面积小,有的色块面积大。色块面积越大,表示着当前这个js体积越大。鼠标放到上面可以看到相关信息,也可以点击放大看里面细分的模块。如下图:鼠标滑过会显示相关js模块的依赖信息.png鼠标滑过会显示相关js模块的依赖信息

如果看官觉得这个页面不够炫酷,可以通过配置template来实现逼格更高的效果。例如:template:'sunburst',对应效果如下图:template值为sunburst时显示旭日图.pngtemplate值为sunburst时显示旭日图

如果看官觉得这个还不够叼,可以这样配置template:'network',此时页面效果如下图:template值为network时页面更加花哨.pngtemplate值为network时页面更加花哨

怎么样?够炫酷了吧!但从易用度上来说,个人觉得后两个模板并没有默认配置的好用。

6.4、想尝试按需加载,但未成功

分析也分析完了,那该如何继续优化这个Vite2项目呢?

答案是:将一些三方库进行按需加载

拿这个项目来说,项目中使用了echartslodashelement-uivxe-table、等一些库。如果不做按需加载,项目的体积会大很多。做了按需加载优化后,项目的体积就会小很多。

如实说,我已经折腾了好些天,也搜索了好多方法,比如:vite-babel-pluginunplugin-vue-components等。

// 两种优化方案的配置如下↓↓↓
import { defineConfig, loadEnv } from 'vite' // vite版本是2.4.4
...... // 省略一些配置

/* 方法一↓↓↓ */
// 文档:https://www.npmjs.com/package/vite-babel-plugin
import babel from 'vite-babel-plugin'
/* 方法一↑↑↑ */

/* 方法二↓↓↓ */
// 文档:https://www.npmjs.com/package/unplugin-vue-components
import Components from 'unplugin-vue-components/vite'
import { ElementUiResolver } from 'unplugin-vue-components/resolvers'
/* 方法二↑↑↑ */

export default ({ mode }) => {
  return defineConfig({
    plugins: [
      /* 方法一↓↓↓ */
      // babel(), // 会读取本地 .babelrc中的配置
      /* 方法一↑↑↑ */

      /* 方法二↓↓↓ */
      // UI框架的自动按需导入
      Components({
        resolvers: [ElementUiResolver()],
      }),
      /* 方法二↑↑↑ */
      ...... // 省略一些配置
    ],
    ...... // 省略一些配置
  })
}

多次打包后体积并没有太明显的变化,这让我有点儿像丈二和尚摸不着头脑!我甚至有点儿怀疑Vite2已经在内部做过了处理。

懵逼中......

如果有大佬熟悉Vite2的按需加载优化,还请不吝赐教,非常感谢!

转载声明:
  若亲想转载本文到其它平台,请务必保留本文出处!
本文链接:/xwzj/2022-08-09/Vite2-optimization-follow-up.html

若亲不想直保留地址,含蓄保留也行。艺灵不想再看到有人拿我的技术文章到他的地盘或者是其它平台做教(装)程(B)而不留下我的痕迹。文章你可以随便转载,随便修改,但请尊重艺灵的劳动成果!谢谢理解。

亲,扫个码支持一下艺灵呗~
如果您觉得本文的内容对您有所帮助,您可以用支付宝打赏下艺灵哦!

Tag: Vue2 后台项目 优化 Vite2 ES6 解构 legacy manualChunks babel 按需加载 loadEnv dotenv

上一篇: 论流氓软件哪家强   下一篇: Vue3学习之ref()用法

评论区