它们都带“js”,但一个是模板,一个是历史,一个是未来。
在 JavaScript 的世界里,文件后缀名从来不只是给开发者看的“备注”,更是给运行环境(浏览器、Node.js)看的“军令状”。今天我们把 .js、.ejs 和 .mjs 拉到一起,进行一次“坦白局”。
1. 元老级选手:.js —— 最熟悉的陌生人
身份定位:JavaScript 的通用脚本文件。
在浏览器时代,.js 就是皇法。但在 Node.js 服务端,.js 的身份一直处于“量子叠加态”:
-
默认立场:在 Node.js 中,默认情况下,.js 被视为 CommonJS (CJS)。这意味着你可以直接用 require() 和 module.exports。
-
可塑性强:.js 的立场受 package.json 中的 "type" 字段影响。
-
如果 "type": "commonjs"(默认) → .js 是 CJS。
-
如果 "type": "module" → .js 摇身一变,变成 ES Module (ESM),可以使用 import/export。
-
核心痛点:正因为 .js 立场摇摆不定,在大型项目中,单看后缀你无法确定它到底是同步加载还是异步加载,容易引发混乱。
2. 模板界扛把子:.ejs —— 披着 JS 皮的 HTML
身份定位:Embedded JavaScript Templating(嵌入式 JavaScript 模板引擎)。
重要提示:.ejs 和 .js/.mjs 完全不是一回事!它不是用来写逻辑代码的,而是用来写 HTML 页面结构的。
-
使用场景:服务端渲染(SSR),比如 Node.js + Express 框架。
-
工作原理:在 .ejs 文件中,你写的是 HTML,但可以通过 <% %> 标签嵌入 JavaScript 逻辑(比如循环、变量输出)。
-
最终归宿:.ejs 文件永远不会被浏览器或 Node 直接执行。它会在后端被 ejs 这个 npm 包编译成纯 HTML 字符串,再发送给浏览器。
<!– 这是一个 .ejs 文件示例 –>
<h1><%= user.name %></h1>
<% if (user.isAdmin) { %>
<button>删除按钮</button>
<% } %>
3. 新时代的“硬规定”:.mjs —— 标准的捍卫者
身份定位:强制使用 ES Module (ESM) 规范的 JavaScript 文件。
为了彻底解决 .js 的“墙头草”问题,Node.js 引入了 .mjs。
-
绝对立场:无论 package.json 里 "type" 写的是什么,.mjs 永远被视为 ESM。
-
语法特权:支持顶层 await;必须使用 import/export;严格模式强制开启。
-
底层逻辑:由于 ESM 是异步加载,.mjs 强制 Node 使用异步解析器去读取文件。
为什么需要它?为了在项目中安全地混用新旧模块。比如你引入一个第三方库,如果它是 .mjs,Node 就知道必须用 ESM 规则去加载它,绝不含糊。
4. 核心对比总结表
| 本质 | JavaScript 源码 | HTML 模板文件 | JavaScript 源码 |
| 运行环境 | Node.js / 浏览器 | Node.js(编译阶段) | Node.js |
| 模块系统 | 不固定(默认 CJS,可切换 ESM) | 无(纯文本渲染) | 强制 ESM |
| 加载机制 | 受 package.json 影响 | 由 ejs 库编译 | 不受 package.json 影响 |
| 核心变量 | 可能有 require 或 import | 只有模板语法 <% %> | 无 require,有 import.meta |
| 主要用途 | 编写业务逻辑 | 编写服务端 HTML 视图 | 编写纯净的现代 JS 模块 |
5. 补充:同样容易混淆的 .cjs
既然提到了 .mjs,就不得不提它同父异母的兄弟 .cjs。
-
.cjs:强制使用 CommonJS 规范。无论 "type" 是什么,它都只能用 require 和 module.exports。
-
一句话关系:
-
.mjs = 强制 ESM(异步、标准化)。
-
.cjs = 强制 CJS(同步、历史遗产)。
-
.js = 随风倒(看 package.json 的脸色)。
-
6. 写在最后:开发时的避坑指南
当你写前端页面(React/Vue):大概率只用 .js 或 .jsx/.ts,因为打包工具(Webpack/Vite)接管了一切,后缀名没那么敏感。
当你写 Node.js 后端:
-
如果你只想写 import 语法,建议直接在 package.json 里加 "type": "module",用 .js 就行。
-
如果你需要同时维护老项目(CJS)和新功能(ESM),强烈建议:新文件用 .mjs,老文件改名为 .cjs。
-
千万别在 .ejs 里写 import,那是徒劳的,它只负责画界面。
遇到报错 Cannot use import statement outside a module:说明你的 .js 文件被当作 CJS 了,要么改后缀为 .mjs,要么去改 package.json。
一句话精辟总结:
.ejs 是画页面的(工匠),.cjs 是开老式燃油车的(保守),.mjs 是开新式电动车的(激进),而 .js 是一辆“油电混动”车,具体烧油还是用电,看你插没插 "type": "module" 这根充电线。



