vue2 项目接入 vite2 参考指南

vue2 项目接入 vite2 参考指南,第1张

本文整理 vue2 项目接入 vite2 需要注意的事项。

基本配置

首先我们需要在项目中安装 vite;其次,要支持 vue2 还需要安装 vite-plugin-vue2

npm install vite vite-plugin-vue2 -D

然后,需要新建一个 vite.config.js 文件替换掉 vue.config.js 文件。其基础内容如下:

import { defineConfig } from 'vite';
import { createVuePlugin } from 'vite-plugin-vue2';

export default defineConfig({
  plugins: [
    createVuePlugin(),
  ],
});

修改 package.json 的运行指令:

  "scripts": {
    "serve": "vite",
    "build": "vite build",
    "build:dev": "vite build --mode dev",
    "build:rel": "vite build --mode release",
    "build:pro": "vite build --mode production"
  }

这里其实就是把 vue-cli-service 替换为 vite

然后,还需要将 public 目录下的 index.html 文件移动到最外层目录下(如果不想移动需要配置 root),然后引入 main.js 文件。

  <body>
    <div id="app">div>
    <script type="module" src="/src/main.js">script>
  body>
常用功能

接下来开始增加常用功能。

1.导入时省略文件类型后缀

我们在导入 vuejs 等文件时往往喜欢省略类型后缀,这需要配置 extensions

export default defineConfig({
  resolve: {
    extensions: ['.vue', '.mjs', '.js', '.ts', '.jsx', '.tsx', '.json'],
  },
});
2.配置路径别名
import path from 'path';
const resolve = dir => path.resolve(__dirname, dir);

export default defineConfig({
  resolve: {
    alias: {
      '@': resolve('src'),
    },
  },
});
3.环境变量

这一功能模块需要注意的地方就比较多了。

实际业务中,往往需要配置以下几个环境文件

.env.development  // 开发环境
.env.dev          // 测试环境
.env.release      // 预发布环境
.env.production   // 线上环境

以开发环境为例,其大致内容如下:

NODE_ENV=development
VUE_APP_ENV=development
...

NODE_ENV 变量将模式设置为 development,VUE_APP_ENV 为自定义变量。
关于这块内容可参考:Vue CLI 模式和环境变量

接下来就要开始说说接入 vite 前后的差异了。

3.1.自定义环境变量前缀

接入前:前缀为 VUE_APP_
接入后:前缀为 VITE_

若不想修改现有代码环境变量的前缀,可以在 vite.config.js 中配置

export default defineConfig({
  envPrefix: 'VUE_APP_',
});
3.2.业务代码中访问环境变量

接入前:通过 process.env.VUE_APP_ENV 可以获取到值。
接入后:通过 import.meta.env.VUE_APP_ENV 获取。

3.3.配置文件中访问环境变量

接入前:在 vue.config.js 中访问,通过 process.env.VUE_APP_ENV 就能获取。
接入后:在 vite.config.js 中访问,不能通过 import.meta.env.VUE_APP_ENV 获取。

那如何解决 vite 的这个问题呢?两种方式:

(1)loadEnv 获取(2)使用 dotenv 插件

(1)loadEnv 获取

import { defineConfig, loadEnv } from 'vite';

function getDefineConfig(env) {
  defineConfig({
    // ...
  });
}

export default ({ mode }) => {
  const env = loadEnv(mode, process.cwd()).VUE_APP_ENV;
  return getDefineConfig(env);
}

注:这里的 mode 就是 env 文件中的 NODE_ENV,即模式。process.cwd() 指当前执行 node 命令时候的文件夹地址,这和 __dirname(被执行的 js 文件的地址)有所不同。

这里我们根据不同环境下的 VUE_APP_ENV 值加载不同的配置。需要注意的是 defineConfig 应只执行一次,否则后一次执行的配置将覆盖前一次执行的配置,而与你最终的 return 无关,比如这样:

function getDefineConfig(env) {
  let devConfig = defineConfig({
    // ...
  });
  let proConfig = defineConfig({
    // ...
  });
  // 不管你 env 的值是啥,最终生效的都会是 proConfig
  return env === 'development' ? devConfig : proConfig;
}

(2)使用 dotenv 插件

先执行安装

npm install dotenv -D

配置如下:

export default ({ mode }) => {
  require('dotenv').config({ path: `./.env.${mode}` });
  return getDefineConfig(process.env.VUE_APP_ENV);
};

这样,我们就能通过 process.env.{configName} 的方式访问了(业务代码中仍然不能这样访问)。不过这种方式需要多安装一个插件,所以推荐使用第一种方法。

4.移除 CommonJS

vite 使用 ESM(import 方式导入)的模块化方案,不支持 CommonJS(require 方式导入) 方案,所以我们不能在业务代码中使用 require

5.html 内容插入

往往我们需要使用 htmlWebpackPlugin 插入一些内容,比如 CDN、线上代码错误监控等。

DOCTYPE html>
<html lang="zh">
  <head>
    
    <% if(htmlWebpackPlugin.options.env==="production") { %>
    <script type="text/javascript">
      // 错误监控相关代码
    script>
    <% } %>
    
    <% for (var i in htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.js) { %>
    <script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>" type="text/javascript">script>
    <% } %>
  head>
  <body>
    <div id="app">div>
  body>
html>

在 vite 下要实现代码挂载的功能该怎么改呢?这里我整理了两种方式:

(1)vite-plugin-html 插件(2)vite-plugin-html-config 插件

(1)vite-plugin-html 插件

安装好插件后,配置:

import { injectHtml } from 'vite-plugin-html';

export default defineConfig({
  plugins: [
    injectHtml({
      injectData: {
        cdn: [
          js: [
            'https://xx/CDN/js/jquery.js',
            // ...
          ],
        ],
        env: '', // 这个值应该是动态的, 这里为了简化 demo, 未做
      },
    }),
  ],
});

在 html 中使用

DOCTYPE html>
<html lang="zh">
  <head>
    
    <% if(env==="production") { %>
    <script type="text/javascript">
      // 错误监控相关代码
    script>
    <% } %>
    
    <% for (var i in cdn&&cdn.js) { %>
    <script src="<%= cdn.js[i] %>" type="text/javascript">script>
    <% } %>
  head>
  <body>
    <div id="app">div>
  body>
html>

通过比较,可以发现,这里省略了 htmlWebpackPlugin.options. 前缀。

相关 ejs 语法可见:github ejs。

当我们要根据环境加载 js 文件时,可这样写:

<%- VUE_APP_TITLE=="production" ? '<script type="text/javascript" src="/webfunny.min.js">script>' : '' %>

需要注意的是使用的是 <%- 开头。

(2)vite-plugin-html-config 插件

安装好插件后,配置:

import htmlConfig from 'vite-plugin-html-config';

const htmlPluginConfig = htmlConfig({
  links: [
    {
      rel: 'stylesheet',
      href: 'https://xx/CDN/css/element-ui2.13.0/index.css',
    },
  ],
  headScripts: [
    `console.log('hi')` // 假设这是错误监控相关代码
  ],
  scripts: [
    {
      src: 'https://xx/CDN/js/jquery.js',
    },
  ],
});

export default defineConfig({
  plugins: [
    htmlPluginConfig,
  ],
});

通过此配置,我们就不用在 html 上做 *** 作了,它会自动将代码挂载到对应位置。
更多配置见:github vite-plugin-html-config

可以根据各自情况选择使用方式,但我个人更偏向方式一。

cdn 这块我们还得做配置,否则打包后的文件仍然包含 cdn 相关的代码。

6.打包时移除 cdn 中的包

需要安装 rollup-plugin-external-globals@rollup/plugin-commonjs 插件

npm install rollup-plugin-external-globals @rollup/plugin-commonjs -D

安装好后做如下配置:

import commonjs from '@rollup/plugin-commonjs';
import externalGlobals from 'rollup-plugin-external-globals';
const externals = {
  vue: 'Vue',
  'vue-router': 'VueRouter',
  axios: 'axios',
  vuex: 'Vuex',
  'element-ui': 'ELEMENT',
  lodash: '_',
  echarts: 'echarts',
  'v-charts': 'VeIndex',
  qs: 'Qs',
};

export default defineConfig({
  build: {
    rollupOptions: {
      external: ['vue', 'vue-router', 'axios', 'vuex', 'element-ui', 'lodash', 'echarts', 'v-charts', 'qs'],
      plugins: [
        commonjs(), // 转换 CJS -> ESM, 通过 cdn 引入的没法转
        externalGlobals(externals),
      ],
    },
  },
});

注:这里的 vueimport xx from yy 中的 yy 包名。Vue 是文件导出的全局变量名字,查看源码或者参考作者文档可以获得。

7.require.context 移除

webpack 中可以通过 require.context 导入模块

let routes = [];
const files = require.context('./modules', false, /(.).js/);
files.keys().forEach(key => {
  const fileData = files(key).default;
  routes[fileData.index] = fileData.routers;
});

vite 也提供了类似功能(详见:Glob 导入),有两种方式:

import.meta.globEager,直接引入所有的模块import.meta.glob,动态导入
let routes = [];
// 方式一
const files = import.meta.globEager('./modules/*.js');
for (const key in files) {
  const fileData = files[key].default;
  routes[fileData.index] = fileData.routers;
}
// 方式二
const files2 = import.meta.glob('./modules/*.js');
for (const key in files2) {
  files2[key]().then(res => {
    const fileData = res.default;
    routes[fileData.index] = fileData.routers;
  });
}
8.静态资源导入

js 引入图片:

import imgUrl from '@/assets/img/logo.png';

css 引入图片:

.logo {
  background: url('@/assets/img/logo.png') no-repeat;
}
9.打包压缩

使用 vite-plugin-compression 做 gzip 压缩。

import compressPlugin from "vite-plugin-compression";

export default defineConfig({
  plugins: [
    compressPlugin({
      filter: /\.(js|css)$/i, // 压缩文件类型
      verbose: true,
      disable: false,
      threshold: 10240,
      algorithm: 'gzip',
      ext: '.gz',
    }),
  ],
});

具体配置可见 github vite-plugin-compression。

10.api 代理

格式和以前没有区别,可以直接将 vue-cli 的 proxy 直接复制过来即可。

export default defineConfig({
  server: {
    host: '0.0.0.0',
    port: 8080,
    proxy: {
      'testapi': {
        target: 'https://api.xx.com',
        changeOrigin: true,
        pathRewrite: {
          [`^/testapi`]: `/testapi`,
        },
      },
    },
  },
});
11.其他问题 1.打包时出现 warning: "@charset" must be the first rule in the file 警告。

解决方法一:

export default defineConfig({
  css: {
    preprocessorOptions: {
      scss: {
        charset: false,
      },
    },
  },
});

方法一不行?再试试方法二:

export default defineConfig({
  css: {
    postcss: {
      plugins: [
        {
          postcssPlugin: 'internal:charset-removal',
          AtRule: {
            charset: atRule => {
              if (atRule.name === 'charset') {
                atRule.remove();
              }
            },
          },
        },
      ],
    },
  },
});
2.Uncaught TypeError: Failed to resolve module specifier “vue”. Relative references must start with either “/”, “./”, or “…/”.

这个是打包后预览项目时控制台出现的错误,出现这个问题的原因是:Vite 不会重写从外部文件导入的内容,我们需要使用支持 ESM 编译的 CDN

这里我们需要用到 https://esm.sh/ 这个网站,然后修改我们的 cdn 配置:

import { injectHtml } from 'vite-plugin-html';

export default defineConfig({
  plugins: [
    injectHtml({
      injectData: {
        cdn: [
          js: [
            'https://esm.sh/vue@2.5.2',
            // ...
          ],
        ],
      },
    }),
  ],
});

https://esm.sh/vue@2.5.2 返回的内容如下:

/* esm.sh - vue@2.5.2 */
export * from "https://cdn.esm.sh/v64/vue@2.5.2/es2021/vue.js";
export { default } from "https://cdn.esm.sh/v64/vue@2.5.2/es2021/vue.js";

另外,我们在挂载

保存