它们都带“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. 核心对比总结表
| 维度 | .js | .ejs | .mjs |
|---|---|---|---|
| 本质 | 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"这根充电线。
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/qq_54203750/article/details/162847476




