首页 / 资讯中心 / 文章详情

Uncaught ReferenceError: require is not defined 的排查与修复全指南

Uncaught ReferenceError: require is not defined 的排查与修复全指南 ★ FEATURED ARTICLE
这个报错我太熟了。每次接到这种“本地毛事没有一打包部署就白屏/报错”的工单顺着控制台扒到最上面十有八九就是这行Uncaught ReferenceError: require is not defined。先说结论这不是你的业务逻辑写崩了也不是打包器坏了而是构建产物里混进了一段“浏览器不认识”的代码。require 这位老兄是 Node.js 专属的模块加载函数它属于 CommonJS 体系浏览器原生环境下压根没有这个全局对象。换句话说你写的是现代前端代码但产出的某个文件被放进了经典时代的环境里执行当然当场翻脸。这篇文章我打算从原理、排查、分场景修复三个方向展开最后把我实测踩过的几个典型坑一并交代。如果你正被这个错卡到半夜跟着走一遍大概率半小时内能找到病根。1. 先搞清楚 require 是谁家的东西为什么浏览器不认1.1 CommonJS 的设计逻辑与浏览器的不适配require 是 CommonJS 规范定义的模块加载函数。Node.js 在这个规范的基础上实现了自己的模块系统每个文件被看作一个模块文件内部可以直接使用 require、module、exports 这几个对象。require 做的事情很简单把目标模块读进来、同步执行、然后返回它的导出对象。关键在于“同步”两个字——代码运行到 require 那一行时运行时必须把依赖的文件内容读完才继续往下走。这个设计在服务端完全没有问题因为 Node 有文件系统读本地磁盘是可控且快速的。但浏览器不行。浏览器没有能力去同步读取一个任意路径的文件更没有一个叫 require 的全局对象。浏览器原生支持的模块化方案是 ES Module也就是我们熟悉的 import / export。所以大家看到这个报错时可以把它当作“API 不存在”处理不是语法错误不是拼写错误是当前运行环境里根本没有这个函数。1.2 打包器做了什么正常产物里不该出现这个错误我们日常用得最多的 webpack、Rollup、esbuild 这类构建工具本质上是做“模块格式桥接”的。它们会扫描整个依赖图把 CommonJS 的 require 和 ES Module 的 import 统一分析最终生成一个或多个 bundle 文件。在产物内部require 会被改写成构建工具自己的模块加载函数比如 webpack 里有webpack_requireRollup 则会把模块静态分析后做提升根本不留运行时加载函数。所以一个正常打包后的前端项目你全局搜索 require( 应该是搜不到裸调用的。一旦在产物、或浏览器执行上下文中出现裸于模块包装层之外的 require几乎可以断定有一小部分代码绕过了构建工具的处理以 CommonJS 的原始形态直接进了浏览器执行环境。为了方便理解我做一个小对比表模块体系加载方式支持环境特点CommonJSrequire 同步加载Node.js运行时才能确定依赖关系ES Moduleimport 静态导入现代浏览器原生支持、构建工具支持编译期静态分析、可摇树优化构建产物内部加载函数浏览器把上述两种都转换成浏览器可执行的形式很多情况下问题不在你写的代码而在“某一步配置让代码以错误格式出来了”。2. 别急着动配置先用两条线索定位 require 的出生地2.1 第一手信息永远来自浏览器控制台看到 Uncaught ReferenceError: require is not defined 时先不要慌更不要随手全局搜 require。第一步是打开控制台右侧的调用堆栈点开展开信息看清楚是哪个文件的哪一行报的错。这里有两种情况一是报错文件在你的源码路径下且看着像 TypeScript 编译出来的 JS。这种情况重点怀疑 tsconfig 的模块配置有问题。二是报错文件在 node_modules 下的某个依赖里或者是 dist 目录里你并不眼熟的第三方产物。这种情况重点怀疑依赖没有进入构建转换管线。如果用了 sourcemap控制台能直接映射回源码定位会更快。没有 sourcemap 的压缩文件也别急可以用格式化工具把报错那段展开看它附近有没有 module.exports、Object.defineProperty(exports, ...) 之类的 CommonJS 特征代码。2.2 双向搜索法源码搜、产物也搜我的习惯是同时在两个地方搜“require(”源码目录里搜确认是不是自己写了 require 但没被转换。构建产物目录里搜确认是哪个文件残留了 CommonJS 代码。以产物目录为例直接用命令行扫grep -rn require( dist/ | head -20如果源码里没有 require 但产物里有那问题基本锁定在构建配置或者某个依赖被原样透传进了产物。接下来要看报错文件是谁再往对应的处理方向排查。2.3 判断报错文件是不是被页面“裸引用”了还有一个高频盲区报错文件并不在你的 bundle 里而是被 HTML 页面用 script 标签直接引用了。常见于某些项目把第三方库或某个编译好的 JS 文件扔进了 public 或 static 目录然后在 index.html 里手动引入。遇到这种情况最快的判断方式是打开浏览器的 Network 面板找到报错对应的 JS 请求看它的 URL 路径。如果路径直接指向类似 /vendor/xxx.js 这种静态目录说明它根本没经过打包器。这样的文件只要是 CommonJS 格式里面出现 require 就是必然的。给一个排查方向的速查表报错文件特征大概率原因优先处理方向自己的源码编译产物TypeScript 模块格式设置错误调整 tsconfig 的 module 配置node_modules 里的依赖第三方 CJS 库未进入转换管线调整构建工具的 commonjs 处理范围构建产物入口文件开头输出库格式选成了 CommonJS调整 output.library.typepublic/static 下的静态文件裸引用了未转换的 npm 包文件换用浏览器版或改为标准 ES Module 引入3. 分四种场景对症下药每个都有标准解法3.1 场景一TypeScript 把 import 编译成了 require这是最经典的一种情况。很多项目习惯把 tsconfig.json 里的 module 设置为 commonjs以为这样更“通用”。但对于纯前端项目来说这是一个隐患tsc 编译时会把源码里的 import 语句统统变成 require()输出 JS 之后如果直接被浏览器加载就是今天这个报错。说白了你要区分运行环境Node.js 环境的服务端代码module 设成 commonjs 没毛病。浏览器端代码module 应该设为 ESNext 或 ES2020 这一类让 TS 保留 import / export 的原始形态交给打包器去处理。我的建议是浏览器端项目的 tsconfig.json 按下面这种写法配置{ compilerOptions: { target: ES2020, module: ESNext, moduleResolution: bundler, strict: true } }注意 moduleResolution 用了 bundler这是现在比较贴近实际打包场景的解析方式。如果你的 TS 版本较老不支持 bundler就用 node 也能跑但尽量升级。有些团队会保留 module: commonjs然后把编译后的文件照样交给 webpack 再转一次这当然也能跑通因为 webpack 认识 require。只是这种链路多绕了一步还容易在某个“绕过打包”的边缘场景里出问题。能直接用 ESNext 就不必折腾。3.2 场景二第三方依赖是 CommonJS 格式没进转换管线npm 上还有大量老牌工具库源码以 CommonJS 形式发布没有提供 ESM 版本或者 ESM 版本标记得不规范。这类库在不同打包器下命运不太一样webpack 5 默认能处理 node_modules 里的 CommonJS一般不会出问题。Vite 开发模式dev用 esbuild 做预构建多数 CJS 依赖会被自动转为 ESM所以本地开发一切正常。但到了 Vite 的 build 阶段Rollup 对某些 CJS 依赖的默认转换范围有限如果某个依赖没有被正确包含产物里就可能残留 require 调用。我踩过的一个真实案例是某老牌工具库它的 package.json 里 main 字段指向 CJS 入口browser 字段却没有配置对应的浏览器版文件。Vite dev 下隐藏得很好一执行 build产物就露馅了。这种场景的修复思路有两种第一种在 Vite 配置里显式让它处理这个依赖import { defineConfig } from vite export default defineConfig({ optimizeDeps: { include: [the-legacy-cjs-lib] }, build: { commonjsOptions: { include: [/node_modules/, /src/] } } })optimizeDeps.include 是告诉预构建器启动时就把这个包转成 ESMbuild.commonjsOptions.include 是让 Rollup 的 commonjs 插件不放过它。两处可以一起配保险系数更高。第二种治本思路优先寻找该库的浏览器版本或 ESM 版本。很多库的官方文档都会区分“Node 版”和“Browser 版”或者提供现代的 ESM bundle。能用 import 直接引的就不要用 CommonJS 入口。3.3 场景三你打的包本身输出成了 CommonJS 格式如果说前两种是“依赖被放过”这种情况纯粹是“输出格式没选对”。当你构建的是一个库想给别人通过 script 标签直接用时如果 webpack 配置里把 output.library.type 设置成了 commonjs 或 commonjs2那产物末尾就是 module.exports xxx。浏览器环境下没有 module、没有 exports、也没有 define肯定直接报错。正确姿势是按使用方式来定输出格式// webpack.config.js module.exports { // 给浏览器直接用 script 引入时用 umd 或 iife output: { filename: my-lib.js, library: { type: umd, name: MyLib } } }umd 的意思是一段“万金油”包装代码在 CommonJS 环境用 module.exports在 AMD 环境用 define在浏览器全局环境直接挂 window。这是兼容性最好的库输出格式。如果你确定目标浏览器都支持 ES Module也可以输出成 module 类型但记得 HTML 里引入时要用 typemodule 的 script 标签不能再用普通 script。3.4 场景四public 目录里裸放了 node_modules 的源文件这种操作我在一些项目里见过因为图省事直接把某个 npm 包 dist 下未经转换的文件拷进 public再在 HTML 里硬引。如果那个文件恰好是 CommonJS 格式或者还依赖了 Node 内置模块不是报 require is not defined就是报 process is not defined。这类问题解决起来最简单但要先统一认识public / static 目录下放的文件浏览器是原样执行的不会经过打包器的魔法。解决方案有三个一是用该库官方提供的“浏览器版”文件这类文件通常用 IIFE 或 UMD 格式打包内部依赖都被处理过了。二是如果它提供了 ESM 版本用 import 方式引入并通过 importmap 之类的机制保持代码清晰。三是把引用它的逻辑改到自己的工程代码里统一经过构建工具处理。这里真正要提醒的是不要出于“临时救急”心理硬塞一个文件到 public 目录那样只是把问题延后了后续维护会变得更混乱。4. 我实测过的三个典型翻车现场4.1 Vite dev 完全正常build 部署后白屏某个项目用了 Vite本地开发 N 个页面跑得行云流水可执行 npm run build 并部署后控制台直接 Uncaught ReferenceError: require is not defined整个应用白屏。排查过程按照我前面说的步骤走了一遍先在产物里 grep require(定位到一个第三方老库的文件再查它的 package.json确认只有 CJS 入口。dev 模式下 esbuild 替它做了预构建build 模式下 Rollup 的 commonjs 插件没覆盖到它于是残留了 require。修复方式就是在 vite.config.ts 里把该库显式加入处理范围。改完之后重新 build再 grep 一遍产物里已经没有裸 require 残留。这个案例的关键教训是dev 模式不报错不代表 build 也 OK构建产物的自检不能省。4.2 为了兼容某个插件把 webpack 的 target 改成了 node一个纯前端项目为了配合某个插件的构建要求开发把 webpack 配置里的 target 从 web 改成了 node。结果 webpack 按 Node 环境的逻辑打包产物里会保留 require 之类的模块引用甚至可能把一些 Node 内置模块的导入原样保留下来。扔到浏览器后require is not defined 如期而至。修复很简单把 target 改回 web// webpack.config.js module.exports { target: web }关键是记住 target 不只是决定输出格式还会影响 webpack 对代码运行环境的假设。前端项目老老实实写 web别为了个别插件乱改。4.3 把某 SDK 的 Node 版文件拷进了 public这是真实发生过的一个案例某地图 SDK 需要引入一个初始化脚本但现场实施图方便从 node_modules 里找到一份“Node 环境专用”的 dist 文件复制到了 public/js/sdk.js然后在 index.html 里用 script 标签引了它。问题在于那份文件是 CommonJS 格式内部第一行就有 require 调用浏览器自然不认识。最后查出来之后去官网下载了真正面向浏览器的版本替换文件问题消失。这个事的本质是你把一个原本经过打包器处理才能运行的模块文件强行放到了不经过打包的静态目录里。这就好比把服务端的代码直接丢给浏览器环境都不一样怎么指望它跑起来。5. 跟 require 绑定的连锁报错一并说清楚5.1 process is not defined、module is not defined 这些邻居require is not defined 通常不会是孤军奋战。一旦某段 CommonJS 代码在浏览器执行它往往还会带动 process is not defined、module is not defined、exports is not defined 等一连串报错。这些报错本质上是一家人都是 Node.js 独有的全局对象在浏览器全局作用域里不存在。如果你看到的报错是多个连在一起的比如先是 ReferenceError: process is not defined后面才跟着 require is not defined那说明问题代码同时用到了 Node 环境变量和 CommonJS 模块机制。处理逻辑不变把它纳入构建工具的正确转换流程或者换成面向浏览器的实现。5.2 webpack 5 移除自动 polyfill 的影响这里补一个背景webpack 5 默认不再为 Node 内置模块自动注入 polyfill。以前很多老库内部使用 process.env、Buffer 之类的东西webpack 4 时代还能靠自动 shim 蒙混过关到 webpack 5 就原形毕露了。当你遇到某个依赖报 process is not defined 时与其折腾 polyfill我更建议先确认这个库是否真的适合浏览器场景。如果它只是偶尔用了一下 process.env.NODE_ENV你可以用 DefinePlugin 在构建时把这个变量替换为实际值// webpack.config.js const webpack require(webpack) module.exports { plugins: [ new webpack.DefinePlugin({ process.env.NODE_ENV: JSON.stringify(production) }) ] }但如果这个库大量使用了 Node 专属能力那就不是 shim 能解决的了趁早换掉为上。5.3 动态 import 和路由懒加载时的次级 chunk 残留还有一种隐蔽情况主入口产物没问题但某个路由懒加载拆出的次级 chunk 里出现了 require。这通常发生在依赖注入或条件加载某个 CJS 库时构建工具没能正确分析它。遇到这种情况先打开 Network 面板找到执行时报错的 chunk 文件看它的来源。确认它来自你自己的异步模块后回到源码找到那个被动态 import 的文件检查是否有 CJS 依赖混入。然后按第三方依赖的处理方案处理。6. 给构建流程加一道“产物自检”从源头堵住这类问题最后分享一个我个人的工程习惯。既然这类问题的核心是“产物里出现了不该有的裸 require”我干脆把它做成了自动化检查每次 build 脚本执行完成后自动扫一遍 dist 目录一旦发现残留的 require(就让构建失败。这样一个原本靠运气和人眼盯的问题就变成了廉价的自动化防线。实现方式很简单在 package.json 里加一个脚本{ scripts: { build: npm run build:proj npm run lint:dist, lint:dist: grep -rn \require(\ dist/ exit 1 || echo \dist OK\ } }这句命令的逻辑是如果 grep 找到了 require(说明产物里有 CJS 残留命令组以成功姿态退出但被你手动 exit 1 拦下来如果没找到则输出 dist OK。当然真实项目里你可能要排除掉某些 UMD 库自带的包装代码具体按实际情况调整匹配规则。但思路是明确且靠谱的在发布前把问题拦在门外。以后再遇到 require is not defined我建议你按顺序做三件事看控制台的调用堆栈定位具体文件确认这个文件是源码产物、第三方依赖还是被静态目录裸引用的漏网之鱼然后分别从 TS 模块配置、构建工具依赖处理范围、输出格式和静态资源放置规则这几个方向去修。多数情况下问题都不是玄学是产物里一段没有被正确转换的 CommonJS 泄漏出来了。
阅读完成 · 觉得有帮助?
咨询建站