本文整理 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>
    <% } %>
    <!-- 使用 CDN 加速的 JS 文件,配置在 vue.config.js 下 -->
    <% 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>
    <% } %>
    <!-- 使用 CDN 加速的 JS 文件,配置在 vite.config.js 下 -->
    <% 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";

另外,我们在挂载 <script> 标签上时,需要加上 type="module" 属性。

参考文档:[Vue3] Vite中使用alias配置项从cdn中加载库文件

另一种解决方法:升级 vue 版本,我选择将 vue 版本升到了 vue@2.6.14,也可解决此问题。

造成此问题还可能是第三方 vue 插件导致,比如我使用了 el-table-infinite-scroll,版本 1.0.10。然后我选择将其替换掉,可解决此问题。

3.Can’t use esbuild as the minifier when targeting legacy browsers because esbuild minification is not legacy safe.

使用 @vitejs/plugin-legacy 插件后打包报此错误。这个应该是该插件的 bug,当时的版本号为 1.4.0

解决方法:升级后解决,升级后的版本为 1.6.4

4.Expected a JavaScript module script but the server responded with a MIME type of “text/html”. Strict MIME type checking is enforced for module scripts per HTML spec.

这个是因为找不到文件,然后被默认为了 text/html 类型。

可能原因之一:本地预览项目时,由于 js 文件被压缩为以 gz 后缀结束的压缩文件,且未保留原 js 文件,浏览器没能正确识别到。

解决方法:修改压缩配置,gzip 压缩应同时保留 js 和 gz 文件。

5. Internal server error: Preprocessor dependency “sass” not found. Did you install it?

需要你安装 sass,我安装的版本为 "sass": "^1.26.5",

安装好后,又出现了新的问题,css 中使用 /deep/ 语法会报错了,然后开始用 ::v-deep 语法进行替换,但这个语法会有问题,发现以前有的 css 代码未生效,然后一顿改。

除了上面的问题,发现以前 css 中的导入语法又有问题,比如:

.bg {
  background: url('~@/assets/img/phone-statusbar.png');
}

我们会在路径前加 ~@ 用来表示根路径,然而现在不行了,需要替换成 @

总结

vite 优点:

  • 使用 vite 构建项目时速度更快(初次很慢),基本都是秒开,开发体验一级棒。

vite 缺点:

  • 兼容性:默认情况下,Vite 的目标浏览器是指能够 支持原生 ESM script 标签支持原生 ESM 动态导入 的。
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐