
浏览器中无法直接通过 import 语句加载仅声明全局变量的传统脚本(如 var feature = {...}),因为 es6 模块系统要求显式导出;必须改造源码添加 export,或借助动态 import() + 全局清理等兼容方案。
浏览器中无法直接通过 import 语句加载仅声明全局变量的传统脚本(如 var feature = {...}),因为 es6 模块系统要求显式导出;必须改造源码添加 export,或借助动态 import() + 全局清理等兼容方案。
在现代浏览器 ES6 模块环境中,import '/feature.js' 语法虽能成功加载脚本,但不会将脚本内声明的全局变量(如 var Feature = {...})自动引入当前模块作用域。这是因为 ES6 模块是封闭的、静态解析的作用域单元——它只识别符合 ES 模块规范的 export 和 import 语句,对传统 IIFE 或全局赋值脚本无感知。
❌ 为什么 import "/feature.js" 不起作用?
该语句仅执行脚本(触发 Feature 在全局挂载),但不建立模块绑定。因此:
import "/feature.js"; console.log(Feature); // ReferenceError: Feature is not defined
✅ 可行的解决方案(按推荐优先级排序)
1. 最优解:改造库源码,添加命名导出(推荐)
若可维护或 fork 该库,在 feature.js 末尾添加:
// feature.js(修改后)
var Feature = { foo() { return "done"; } };
export { Feature }; // ✅ 显式导出
然后在你的模块中正常导入:
// index.js
import { Feature } from "/feature.js";
Feature.foo(); // ✅ 正常调用,零全局污染
? 提示:若库支持 UMD(如你提到的 exports.Gmail = Gmail),说明它已具备模块化基础,只需补全 ES 模块导出即可。
2. 兼容方案:动态导入 + 显式提取 + 清理全局(适用于不可修改库)
利用 await import() 触发执行,并从 window 中读取变量,随后可选择性删除以避免污染:
// index.js
async function loadFeature() {
await import("/feature.js"); // 执行脚本,挂载 Feature 到 window
const Feature = window.Feature;
delete window.Feature; // 主动清理,保持模块纯净
return Feature;
}
// 使用
const Feature = await loadFeature();
Feature.foo(); // ✅
console.log(window.Feature); // undefined(已清理)
3. 关于 exports 工作区的警示
你提到的 globalThis.exports = {} 方案存在严重缺陷:
- exports 是 Node.js 环境变量,在浏览器中非标准,强行挂载会污染全局;
- 多个库共用 exports 可能引发覆盖冲突;
- 违背模块封装原则,丧失可预测性。
✅ 正确做法是:永远避免向 globalThis 写入非标准属性。如需桥接 UMD 库,应使用 <script type="module"> 配合 window 读取,或封装为独立初始化函数。</script>
总结
| 方案 | 是否推荐 | 关键特点 |
|---|---|---|
| 修改源码添加 export | ✅ 强烈推荐 | 符合标准、类型安全、Tree-shaking 友好 |
| 动态导入 + window 提取 + 清理 | ⚠️ 临时兼容 | 无需改库,但需手动管理生命周期 |
| 注入 exports 或 globalThis 属性 | ❌ 禁止 | 破坏环境一致性,易引发冲突 |
最终,ES6 模块的设计哲学是“显式依赖、作用域隔离”。面对非模块化脚本,真正的长期解法永远是推动其模块化升级——而非绕过规范做脆弱的全局桥接。











