ReferenceError: exports is not defined问题解决方案

1,240 阅读3分钟

ReferenceError: exports is not defined问题解决方案

我相信绝对不止我遇到过这个问题:

image.png

这个问题出现的时机一般是本地dev环境没有报错,而构建线上环境时才会出现,这时一般是打算提测或者合并代码的时间,相信一般不会有人愿意在这个时间点花大把时间去扒webpack、vite、tsc、babel等等工具的问题,也不会有人愿意去鼓捣可能涉及全局的构建配置,只想尽快解决问题,因为deadline马上要到了!!! : (

所以我这里直接给出两个可能的原因,希望对大家有帮助

typeof 导致的问题

检查保留了exports的代码的那个文件,搜一下是否有用到 typeof xxx 的语法,那么大概率是这个问题,将其改为 Object.prototype.toString.call(value) 来判断类型即可,当然这两个还是有些差异的,最好封装一个 typeOf(value) 的公共函数。

const supportTypes = {
    String: 'string',
    Number: 'number',
    BigInt: 'bigint',
    Boolean: 'boolean',
    Symbol: 'symbol',
    Undefined: 'undefined',
    Object: 'object',
    Function: 'function',
    GeneratorFunction: 'function',
} as const
declare type ValuesOf<T> = T[keyof T]; // 这个可以定义到全局,看你心情

type Types = ValuesOf<typeof supportTypes>
/**
 * 如果你构建完后的代码出现`ReferenceError: exports is not defined`错误,那么极有可能你写了typeof someValue导致了babel的编译bug,
 * 可以替换为这个函数来回避这个问题
 * @param value 
 * @returns 
 */
export function typeOf(value: any): Types {
    const type = Object.prototype.toString.call(value).slice(8, -1)
    return supportTypes[type] || 'object'
}

type TypeMap = {
    string: string,
    number: number,
    bigint: bigint,
    boolean: boolean,
    symbol: symbol,
    object: object,
    undefined: undefined,
    function: Function,
}

/**
 * 判断是否是某个类型,用于实现 typeof value === 'string' 后,会识别value的类型的能力
 * @example
 * ```
    let value: (() => void) | number

    if (typeIs(value, 'function')) {
        console.log(value) // value 可被识别为 function
    } else {
        console.log(value) // value 可被识别为 number
    }
 * ```
 * @param value 
 * @param type 
 * @returns 如果是,返回true,且会断言value为指定类型
 */
export function typeIs<T extends Types>(value: any, type: T): value is TypeMap[T] {
    return typeOf(value) === type
}

typeIs 是模仿 typeof 判断后,在其内部作用域可以被typescript识别为指定类型用的。

多级嵌套作用域的闭包捕获变量问题

在写这篇文章之前,我遇到这个问题都是typeof导致的,直到今天被狠狠的教育了一把。

我的代码结构大致是这样的:

image.png

看起来很正常的代码,但就是报了这个错。

原因在内层的setTimeout里面的函数闭包捕获了外面至少两层以上的变量进来导致了这个报错,解决方式也简单,把外部变量通过参数传入内部闭包即可,减少了一层闭包,它神奇的就好了。

setTimeout((size, arr) => {
    // 可以用size和arr了
}, 500, size, arr)

写在后面

如果以上两个可能的原因都无法解决你遇到的问题,那可能就要痛苦的排查了,我的排查方法非常原始,就是定位到文件是哪个(报错的位置就是那个文件的位置),然后一半一半的删代码重新构建,不出几次就能定位到大致位置,再看看符不符合现有的经验去排查。

还有就是构建一次必然会花费大量时间,此时也可以将无关的代码入口都屏蔽掉,只关注这个文件,能减少很多时间的浪费。

最后附上当时排查时查阅的一些资料:

  1. github.com/vitejs/vite…
  2. github.com/rollup/roll…

然并卵,后续还是需要静下心来抽时挖一挖根本的问题在哪里,但可以预见的是,事后大概率好了伤疤忘了疼(逃~