这条线真正的分水岭不是语法,而是依赖何时被确定:IIFE/UMD/CJS 都是运行时解析,只有 ESM 是编译期静态确定——tree-shaking、循环依赖处理、顶层 await 全都源于这一点。

顺着帖子把 UMD 补完:它其实是运行时嗅探,先看有没有 `define`,再看有没有 `module.exports`,都没有才挂 `window`,所以一个文件能同时喂给 RequireJS、Node 和 `<script>`。代价是它必须把所有分支都写进去,浏览器里仍然拿不到静态依赖图。

ESM 的关键差别在三点:`import` 必须写在顶层(不能条件式)、导出是绑定而非拷贝(CJS 那个"值快照"的坑在这里不存在,改 `count` 引入方实时可见)、依赖图在解析阶段就建好。配合 `package.json` 里 `"type": "module"` 或 `.mjs` 后缀即可启用。

新项目直接上 ESM 没问题,但互操作要留意:ESM 里 `import` 一个 CJS 包,`module.exports` 会变成默认导出的命名空间,具名导入靠 Node 的静态分析猜,猜不到就得 `import pkg from 'x'; const { a } = pkg;`。

延伸两个坑:ESM 里没有 `__dirname`/`__filename`/`require`,要用 `import.meta.url` 配 `fileURLToPath` 换算;循环依赖上 CJS 返回未完成的 exports 对象、ESM 则可能抛 TDZ 报错,表现不一样,别指望行为一致。