export 本身不是适配器而是导出器,跨平台适配必须在导出前完成:通过运行时环境检测+动态 import 加载对应平台模块,再统一导出接口;推荐主入口条件分发+package.json 条件导出实现构建无关的多环境支持。

直接用 export 语法本身无法实现跨平台自动适配——它只是导出声明的机制,不是环境判断或逻辑分支的工具。真正起作用的是“在导出前,先做平台识别与条件封装”,再通过标准化的导出方式暴露统一接口。
明确 export 的定位:导出器,不是适配器
export(无论 JavaScript、C++20 还是 Go)只负责“让什么可见、以什么形式可见”。它不执行判断、不读取运行时环境、也不加载不同文件。跨平台适配必须发生在 导出之前,例如:
- 在入口文件里检测
typeof window !== 'undefined'或process.platform - 根据条件动态
import()对应平台的实现模块 - 将适配后的逻辑结果,用
export default或export const xxx统一导出
推荐做法:用条件加载 + 单一导出接口
以数据采集模块为例,可组织为:
-
src/collector/index.ts:主入口,不做具体实现,只做环境分发 -
src/collector/node.ts:基于fs、child_process的采集逻辑 -
src/collector/web.ts:基于navigator.sendBeacon、PerformanceObserver的逻辑 -
src/collector/rn.ts:适配 React Native 的原生模块桥接调用
主入口内这样写:
src/collector/index.tslet collector;<br>if (typeof window !== 'undefined' && !window?.__RN_ENV) {<br> collector = await import('./web').then(m => m.webCollector);<br>} else if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') {<br> collector = await import('./rn').then(m => m.rnCollector);<br>} else {<br> collector = await import('./node').then(m => m.nodeCollector);<br>}<br><br>export const start = collector.start;<br>export const stop = collector.stop;<br>export const flush = collector.flush;
配合 package.json exports 实现构建无关的多环境支持
若需支持 ESM/CJS 双环境,且希望不同平台走不同物理入口(如 RN 项目强制走 ./dist/index.rn.cjs),可在 package.json 中使用条件导出:
"exports": {<br> ".": {<br> "import": "./dist/index.mjs",<br> "require": "./dist/index.cjs",<br> "react-native": "./dist/index.rn.cjs",<br> "browser": "./dist/index.web.mjs"<br> }<br>}
打包工具(如 Metro、Vite、Webpack)会识别这些条件,并在对应环境中自动解析到正确路径。此时每个 .cjs 或 .mjs 文件内部,已预先完成了平台逻辑封装和导出。
避免常见误区
以下做法不可靠或不可维护:
- 在
export语句中直接写三元判断:export const api = isWeb ? webApi : nodeApi;—— ESM 不允许在顶层导出中执行表达式 - 用
process.env.NODE_ENV区分 Web/RN/Node —— 构建时静态替换,无法应对运行时动态切换(如 PWA 离线后切回本地采集) - 把所有平台逻辑塞进一个文件再用
if/else分支导出 —— 增加包体积,且 Tree-shaking 失效










