背景
项目在开发环境能正常运行,但是导build至生产模式报错
node_modules/rc-util/es/ref.js (3:21): “isMemo” is not exported by "node_modules/react-is/index.js", imported by “node_modules/rc-util/es/ref.js”.
is not exported ,猜测肯定时commonJS和ES6兼容问题,Vite是处理ESModule,对于commonJS需要转换
解决
起初猜测是版本不匹配(React&AntDesign),尝试了,还是不行
再想着是不是Vite
的原因:
最开始我配置转换是用@rollup/plugin-commonjs
(哈哈哈,AI帮配置的)
import commonjs from ‘@rollup/plugin-commonjs
’
export default defineConfig({
base: ‘/’,
plugins: [react(),
commonjs
({
include: /node_modules/,
requireReturnsDefault: ‘auto’,
extensions: [‘.js’, ‘.cjs’],
transformMixedEsModules: true,
dynamicRequireTargets: [‘node_modules/rc-util/es/ref.js’]
})],
resolve: {
alias: {
‘@’: path.resolve(__dirname, ‘./src’),
}
},
后来再gpt了一下,尝试用vite-plugin-commonjs
,结果就好了
import commonjs from ‘vite-plugin-commonjs’;
export default defineConfig({
base: ‘/’,
plugins: [react(),commonjs()],
})
主打越简便越好
why
概要
在使用 @rollup/plugin-commonjs
打包时,由于该插件基于静态 AST 分析 CommonJS 模块的导出,且从 v11 开始移除了 namedExports
选项,它无法自动检测 react-is
在其 CommonJS 入口文件 index.js
中的诸如 isMemo
等命名导出,因此会报错
'isMemo' is not exported by node_modules/react-is/index.js
(Stack Overflow, GitHub)。
而 vite-plugin-commonjs
则在 Vite 开发模式下采用纯 JavaScript 的按需拦截与转换方式,支持更宽松的动态 require
解析与导出提取,因而能够正确地将 react-is
的所有导出(包括 isMemo
)转换为 ESM,从而避免上述错误。(npm, GitHub)。
问题分析
1. react-is
的 CommonJS 结构
react-is
在其index.js
中通过module.exports = require('./cjs/react-is.development.js')
等方式将所有 API 输出到一个对象上,其中包含isMemo
、isFragment
、ForwardRef
等多种导出。(Bitbucket)- 由于这些导出并非在
index.js
处以静态exports.foo = …
形式出现,Rollup 的 CommonJS 插件在静态分析时无法识别它们。(GitHub)
2. @rollup/plugin-commonjs
的静态导出检测局限
- 插件默认依赖静态 AST 来寻找
module.exports
与exports.*
,对深层或间接导出缺乏动态检测能力。(Stack Overflow) - 在 v11 版本中移除了用于手动指定导出的
namedExports
选项(PR #410),进一步减少了对这类场景的兼容;如果不手动回退到旧版或采用插件配置 mapping,就无法提取isMemo
。(GitHub, GitHub) - 因此,当 Rollup 尝试从
react-is/index.js
导入isMemo
时,会报出“未导出”错误,并终止打包。(Stack Overflow)
vite-plugin-commonjs
的工作原理
1. 纯 JS 按需拦截
- 插件在 Vite 开发服务器中拦截对 CommonJS 模块的请求,按需对源码进行字符串或轻量 AST 转换,而非整体打包。(npm)
- 它默认对
node_modules
下的依赖(包括react-is
)启用转换,并能够识别模块导出对象上的所有属性,无需额外配置。(npm)
2. 动态 require
支持
- 支持类似 Webpack 的动态
require('./foo/' + bar)
形式,并提供dynamic.loose
等选项以控制匹配方式。(npm) - 通过执行时或准运行时的方式(而非纯静态分析),能够提取如
isMemo
这类间接或动态导出,从而生成完整的 ESM 接口。(npm)
为什么切换后问题消失
-
导出识别更全面
vite-plugin-commonjs
并不依赖旧版namedExports
,而是对module.exports
对象执行更灵活的提取——包括遍历所有属性并生成相应的export
语句。(npm) -
无需额外配置
相比必须在 Rollup 中显式声明commonjs({ namedExports: { 'node_modules/react-is/index.js': ['isMemo'] } })
的做法,Vite 插件开箱即用即可支持
react-is
的所有导出。(GitHub) -
性能与体验
在开发模式下,vite-plugin-commonjs
的按需即时转换更加轻量快速,不影响 HMR 和冷启动速度。(vitejs)
建议
-
开发环境:继续使用 Vite 默认的
esbuild
+vite-plugin-commonjs
预构建方案,无需手动干预。 -
生产构建:若仍使用 Rollup,可考虑在
build.rollupOptions.plugins
中回退到支持namedExports
的旧版@rollup/plugin-commonjs
(如 v10),或对react-is
做显式映射:commonjs({ include: 'node_modules/**', namedExports: { 'node_modules/react-is/index.js': ['isMemo'] } })
-
升级规划:留意 Rollup 插件未来对 CommonJS 动态导出的支持情况,或考虑将
react-is
替换为 ESM 原生模块版本(若可用)。
以上即为何更换至 vite-plugin-commonjs
后,isMemo
能被正确导出,而使用 @rollup/plugin-commonjs
时却报错的根本原因。
Vite原理
摘要
本文将深入剖析 Vite 的底层原理,涵盖其设计理念、模块加载与依赖预构建机制、开发服务器架构、模块图与热模块替换(HMR)、生产构建流程及插件系统等核心组成部分。(vitejs, 知乎专栏)
通过原生 ESM、esbuild 预构建、基于 Koa/connect 的请求拦截与按需编译,以及在生产环境中集成 Rollup,Vite 实现了极致的冷启动速度与高效的开发体验,同时保证了生产构建的优化与稳定性。(vitejs, vitejs)
设计理念
原生 ESM 与现代浏览器
现代浏览器已原生支持 ES6 模块,Vite 充分利用这一特性,直接在浏览器中按源码加载模块,省略传统打包过程,实现“零打包”开发体验。(掘金, xiaohanglin.site)
依赖预构建
在启动阶段,Vite 会使用 esbuild 对 node_modules
中的第三方依赖进行预构建,将它们转换为快速加载的 ESM 格式,减少浏览器请求次数并加速冷启动。(vitejs, sii.pl)
开发服务器架构
请求拦截与按需编译
Vite 启动一个基于 Koa 或 connect 的开发服务器,拦截所有模块请求,当遇到 import
语句时才实时编译并返回 ESM 代码,无需整体打包,真正实现按需加载。(掘金, 腾讯云 - 产业智变 云启未来)
模块图(Module Graph)
Vite 在内存中维护一个全局模块图,用于追踪模块之间的依赖关系和状态,当文件更新时,只重新加载受影响的模块,提升刷新效率。(vitejs)
热模块替换(HMR)
通过在客户端与服务端之间建立 WebSocket 连接,Vite 能够在源文件发生修改时快速推送更新,仅刷新变更模块,确保热更新速度和反馈即时性。(博客园, 掘金)
生产构建流程
集成 Rollup 打包
在生产模式下,Vite 调用 Rollup 作为底层打包器,利用其成熟的插件生态、Tree-shaking 与代码分割能力,生成可在静态托管环境下高效运行的资源包。(vitejs, vitejs)
代码分割与 Tree-shaking
Rollup 会根据 ESM 语法分析出未使用的代码并剔除,结合动态 import()
,进行自动代码分割,实现按需加载和最小化包体积。(vitejs, Vite)
插件机制
Vite 的插件系统基于 Rollup 插件 API 扩展,支持自定义模块解析、加载、转换等各环节的钩子,同时提供虚拟模块(virtual modules)机制,极大增强了可扩展性与灵活性。(vitejs, vitejs)
性能优化策略
冷启动优化
通过依赖预构建与原生 ESM,Vite 启动时只需解析并加载必要模块,可在毫秒级完成冷启动,大幅优于传统打包工具的编译等待。(vitejs, vitejs)
构建速度优化
在开发阶段借助 Rust 编写的 esbuild 实现超高速 JS/TS 转换,在生产构建中分离 JS 与 CSS 处理,进一步提升整体构建速度。(vitejs, 掘金)
小结
Vite 通过创新的原生 ESM 加载、esbuild 预构建、按需编译与请求拦截,以及 Rollup 生产构建与灵活插件系统,实现了快速冷启动、即时 HMR 和高效构建的完美平衡,堪称现代前端开发的理想工具。(xiaohanglin.site, vitejs)