Vite 踩坑指南

18,916 阅读4分钟

xxx does not provide an export named 'xxx'

大部分第三方包都是 cjs 导出的,也就是只有一个导出口,比如 axios 、jquery、lodash等,他们的导出方式类似下面这样

module.exports = require('./xxx');

显然,这并不能被 Vite 识别,因为 Vite 只支持 ESM 的导出方式,这部分第三方包需要做个兼容。

好在现在Vite有一个预编译功能,会把这些第三方包预编译一遍,转换为ES Module,放置在node_modules/.vite文件夹下。类似下面

image.png

所以对于这些第三方包,如果是commonjs的规范,我们不用管Vite默认会帮我们处理

Uncaught ReferenceError: require is not defined

这个问题应该是出现最多的一个问题了,原因在于 Vite 是完全依靠 ESM 原生能力的,也就是他只认识 import ,因为 Vite 依赖 scriptmodule 属性。我们的代码最终都会被送到浏览器里执行,requirecjs 的关键词,浏览器环境本身就没定义这个方法,自然就报错了。这里和 webpack 不一样,webpack 把文件送到浏览器之前是会进行预打包的,这时候已经将 require 转换成 浏览器能兼容的方法了。(这个出现的原因可能是依赖中通过import导入一个ES6模块,但是这个ES6模块中使用了require,当Vite去构建的时候,发现是采用import导入的就不会预编译,从而报错)

如果是自己写的模块,改成import导入即可,如果是第三方模块,可能需要配合patch-package对源码打一个补丁

antd3

image.png

通过调用栈我们发现是在node_modules/antd/es/icon/index.js中写了如下代码

import * as allIcons from '@ant-design/icons/lib/dist';
// ...
ReactIcon.add.apply(ReactIcon, _toConsumableArray(Object.keys(allIcons).map(function (key) {
  return allIcons[key];
})));

而问题就出现在这个Object.keys(allIcons),allIcons中会有一个default的属性,这个属性应该是打包工具帮我加上的,但是就是因为这个里面多了一个default属性,导致下面的代码有问题,所以我们可以通过patches对源代码做一个补丁操作,具体原因可以查看文章使用 Vite 过程中遇到的 CommonJS 兼容问题

import allIcons from '@ant-design/icons/lib/dist';

global is not defined

这是因为在浏览器中,并没有global这个全局属性 在index.html中加入

<!-- index.html -->
<html>
  ...
  <body>
    <script>
      global = globalThis
    </script>
    ...
  </body>
</html>

Error: 'CombineService' is not exported by node_modules/@umijs/use-request/lib/types.js, imported by node_modules/@umijs/hooks/es/useFormTable/index.js

这个是在使用@umi/hooks中,里面的node_modules/@umijs/use-request/lib/types.js文件为一个空文件,没有导出,我们也可以通过给源码包打一个补丁

module.exports = {};

decorators not support in js for prebuild

目前esbuild 也还不支持在JS中使用装饰器的功能,因为目前装饰器还没有被写入到ES标准中,但是我们的项目中可能已经使用了装饰器,例如使用了mobx的项目,那么我们需要使用TS来支持装饰器。 相关issue: github.com/vitejs/vite… github.com/evanw/esbui…

Dynamic require of "node_modules/antd/lib/style/default.less" is not supported

当我们使用antd时,引入组件

import {Menu} from 'antd';

但是我们默认引入的组件是从node_modules/antd/lib中引入组件的,而这个里面的组件引入样式都是如下方式引用。

"use strict";

require("../../style/default.less");
require("./index.less");
require("../../tooltip/style");

我们需要安装vite-plugin-importer,然后在vite.config.js中进行如下配置

// vite.config.js
import usePluginImport from 'vite-plugin-importer';
import { defineConfig } from "vite";

export default defineConfig({
    plugins: [
        usePluginImport({
          libraryName: 'antd', 
          libraryDirectory: "es",
          style: 'css',
        }),
    ],
})

这样我们加载模块就是从es文件夹下引入

import '../../style/default.less';
import './index.less'; // style dependencies
import '../../tooltip/style';

环境变量注入

在之前webpack项目中,我们会通过process.env.xxx来设置环境变量,process对象在浏览器环境中并不存在,是Webpack帮我们进行了转换,在使用Vite的时候,我们也可以使用类似的配置

{
    define: {
        'process.env.APP_ENV': JSON.stringify(process.env.NODE_ENV),
        'process.env.APP_REGION': JSON.stringify(process.env.APP_REGION),
    }
}
export const APP_REGION = process.env.APP_REGION || 'id';
export const APP_ENV = process.env.APP_ENV || 'test';
export const SENTRY_RELEASE = process.env.SENTRY_RELEASE || 'dev';
export const NODE_ENV = process.env.NODE_ENV || 'development';

注意这里Vite会将process.env.APP_REGION当做一个字符串进行替换,所以我们在使用的时候不能使用这种结构的方式来进行取值const { APP_REGION } = process.env;

当然我们也可以使用.env的配置方式

vite --mode dev

然后提供.env.dev

VITE_APP_ENV='dev'
VITE_APP_REGION='id'

如果需要多模式怎么办,例如 node 环境和国家,是两种平行的模式 环境变量和模式

插件

vite-plugin-optimize-persist

一个是预构建插件 vite-plugin-optimize-persist。在我们的业务代码中会有许多动态依赖项,我们一个个去设置到vite的配置optimizeDepsinclude中很麻烦,尤其有的需要设置到具体文件路径而不是只设置到根目录,不设置的话在开发时当第一次加载这些时还是需要等待漫长时间的白屏时间等待其进行预构建。此插件会将动态依赖项自动分析添加到optimizeDepsinclude中,这样别人下载源码第一次运行即使没有缓存也无需等待。

解决的问题:有些页面是通过懒加载,同样加载里面的依赖时候,也是会进行预编译的,而这个过程就会等待,导致页面白屏的问题。这个插件就是启动的时候,必须提前编译好这些依赖,而这些依赖都是会自动写入到package.json vite字段的。