Node.js服务端无法条件使用export 如何实现前后端代码共享
我有一个名为 global-namespaces.js 的模块文件,其中包含大量常量与若干基础函数。目前我正尝试升级客户端侧代码,使其支持modules与import语句。顺带说明,我知道下方代码写法看起来较为别扭,这是因为我目前仍在摸索可行的解决方案。
服务端侧我原本直接使用*require()*引入模块,但始终找不到合适的方式从global-namespaces.js中导出所需符号,使其在客户端上下文可用的同时不会在服务端触发语法错误。我无法将export关键字或导出块放置在IF判断语句内,该写法会直接触发语法错误。以下是我当前的代码:
try { g_GlobalNamespaces = new GlobalNamespaces(); } catch(err) { console.error(`${errPrefix}Error occurred during the creation of the global GlobalNamespaces object.`); console.info(errPrefix + `err object:`); console.dir(err, {depth: null, colors: true}); } // 同时适配客户端和服务端逻辑,判断当前是否为服务端环境 if (typeof module == 'undefined' || typeof module.exports == 'undefined') { // 客户端环境:g_GlobalNamespaces 已经挂载到全局命名空间 } else { // 服务端环境:按照CommonJS规范导出,适配require()调用 module.exports = { g_GlobalNamespaces: g_GlobalNamespaces } } // 尝试导出适配import语句,这行代码在服务端会直接触发语法错误 export { g_GlobalNamespaces }
我查阅的相关技术帖子中,一则尚无有效回答,主题为「Node.js下如何实现客户端和服务端代码共享」;另一则仅处理纯客户端侧的条件导出场景,无法解决前后端代码共享的问题,主题为「ES2015中的条件导出实现」。
核心诉求:如何组织global-namespaces.js的代码结构,才能让这份代码同时兼容服务端与客户端的运行环境?
触发语法错误的核心原因是ES Module的export属于静态声明语法,必须写在模块顶层,无法被条件判断语句包裹。CommonJS环境在解析代码阶段遇到顶层的export关键字就会直接抛出语法错误,根本不会执行后续的条件判断逻辑。可以根据项目实际情况选择以下两种改造方案:
方案1:UMD同构写法(单文件兼容所有环境,无需额外构建工具)
直接在文件中检测当前运行时的模块规范,统一封装导出逻辑,从根源上避免非ESM环境遇到export关键字触发错误:
// 先完成核心逻辑初始化 try { var g_GlobalNamespaces = new GlobalNamespaces(); } catch(err) { console.error(`${errPrefix}Error occurred during the creation of the global GlobalNamespaces object.`); console.info(errPrefix + `err object:`); console.dir(err, {depth: null, colors: true}); } // 多环境导出适配 (function (root, factory) { // 适配Node.js/CommonJS环境(服务端require()调用) if (typeof module === 'object' && typeof module.exports === 'object') { module.exports = factory(); } // 适配AMD加载器环境(可选,无相关需求可直接删除该分支) else if (typeof define === 'function' && define.amd) { define(factory); } // 适配浏览器全局环境(客户端script标签直接引入) else { root.g_GlobalNamespaces = factory().g_GlobalNamespaces; } })(typeof self !== 'undefined' ? self : this, function() { // 统一返回所有需要对外暴露的变量、方法 return { g_GlobalNamespaces: g_GlobalNamespaces }; });
这种写法不需要任何编译步骤,单文件可直接在三类场景下运行:服务端require()引入、浏览器端<script>标签直接引入、打包工具处理ESMimport引入,不会出现语法报错。
方案2:统一ESM规范+多产物构建
如果你的服务端运行环境为Node.js 14及以上版本(已原生支持ESM规范),可以直接把项目模块统一改造为ESM规范:
- 将文件后缀改为
.mjs,或者在项目package.json中添加"type": "module"配置 - 移除原代码中
module.exports相关的CommonJS导出逻辑,直接使用顶层export语法做导出 - 浏览器端引入脚本时给
<script>标签添加type="module"属性即可
如果需要兼容旧版Node.js的CommonJSrequire()调用,可以使用esbuild、Rollup等构建工具,在构建环节同时输出CommonJS和ESM两份产物,分别对应服务端和客户端场景使用。
注意:任何将
export写在条件判断、函数内部等非顶层位置的写法都不符合ESM规范,会直接触发语法错误,和运行环境无关,不要尝试这类写法。
内容的提问来源于stack exchange,提问作者Robert Oschler

