Visual Studio Code
代码编辑器 · 扩展 · 调试
写脚本、网页和多数语言都能先用它,扩展多、启动快。
正在打开工具详情…
在电脑上跑 JavaScript,前端工具链和不少开发服务器都靠它。
做网页、用 npm 装工具,或课程要求 Node。
只写 Python 或只改静态 HTML,可以后装。
Node.js 让 JavaScript 能跑在浏览器之外:服务端、命令行工具、构建脚本都用它。它解决的是「前端项目需要一套本地运行环境,以及一个装包和跑脚本的入口」。
装了它就同时有了 node 与 npm:npm install 装依赖,npx 直接跑工具,package.json 记录脚本和版本。绝大多数前端构建工具、脚手架和本地开发服务器都建立在它之上,做网页开发基本绕不开。
代价要提前知道:版本迭代快,老项目在新版 Node 上可能跑不起来,通常要配 nvm 这类版本管理器来切换;node_modules 体积大、依赖树深,装包前看清来源——供应链风险主要出在这一层;另外它是单线程事件循环,CPU 密集任务是短板。
许可证为 MIT(仓库内含第三方组件,各自另有许可)。安装包从 nodejs.org 下载,一般选 LTS 比 Current 更稳;Windows、macOS、Linux 都有。
上手步骤之外的具体做法:常见场景怎么配、命令怎么写、报错怎么处理。
以 ESM 与 CommonJS 的判定规则为主线,说明 `type` 字段、扩展名与 `__dirname` 的关系,并给出 `npm ci`、循环依赖等工程实践要点。
这是两套系统并存带来的最大困惑。判定规则看扩展名和最近的 package.json 里的 type 字段:
| 情况 | 结果 |
|---|---|
.cjs 文件 | 一定是 CommonJS |
.mjs 文件 | 一定是 ESM |
.js,最近的 package.json 有 "type": "module" | ESM |
.js,最近的 package.json 有 "type": "commonjs" | CommonJS |
.js,往上找不到 package.json 或没有 type | CommonJS(但若含 ESM 语法会按 ESM 解析) |
关键点:type 只看最近的祖先 package.json,不会向上合并。所以子目录放一个自己的 package.json 就能让该目录下的 .js 变成另一种模块——这既是灵活的隔离手段,也是「为什么这个文件突然报 require is not defined」的常见原因。
官方明确建议:即使全是 CommonJS 的包,也把 "type": "commonjs" 写进 package.json,写明确比让构建工具猜要省事。
require() 永远走 CommonJS 加载器,import() 永远走 ESM 加载器,两者互不切换。
__dirname 在 ESM 里不存在__dirname 和 __filename 是 CommonJS 的模块作用域变量,ESM 里没有。报错就长这样:
textReferenceError: __dirname is not definedESM 的等价物:
javascript// __dirname 的替代
import { fileURLToPath } from 'node:url'
import { dirname, join } from 'node:path'
const __dirname = dirname(fileURLToPath(import.meta.url))
const dbPath = join(__dirname, 'data.db')CommonJS 用 require/module.exports,ESM 用 import/export。混写会直接报错:
javascript// ESM 里没有 module
export const x = 1 // 正确
// CommonJS 里没有 import
const y = require('./y') // 正确三个实际会撞上的点:
await 只有 ESM 有。 CommonJS 里用 await 会语法错误。import './utils' 在 ESM 里会报错,要写 import './utils.js'——ESM 不做扩展名补全。node: 前缀更稳。 require('node:fs') 明确走内置,不受 name 字段之类的影响。require 加载 ESM较新版本 Node 支持用 require() 加载 ESM(有版本下限要求),但有两个硬限制:模块必须完全同步(不含顶层 await),否则抛 ERR_REQUIRE_ASYNC_MODULE。此时应改用 import()。
返回值是模块命名空间对象,默认导出会落在 .default 属性上:
javascript// point.mjs
export default class Point {}javascriptconst point = require('./point.mjs')
point // [Module: null prototype] { default: [class Point] }
point.default // class Point ← 真正的类在这里这一点最容易让人以为加载失败了。ESM 里若显式导出 'module.exports' 名字,可以指定 require() 返回什么,但那会让命名导出对 CommonJS 消费者不可见。
bashnpm install # 按 package.json 装,必要时更新 lock
npm ci # 严格按 lock 装,不改 lock,CI 与 Docker 里用这个CI、构建产物、Docker 里一律用 npm ci。 它要求 package-lock.json 与 package.json 一致,不一致就直接失败——这比装出一个与开发机不同的依赖树要好。package-lock.json 必须提交进版本库。
排查依赖问题:
bashnpm ls # 看依赖树,找重复版本
npm ls <包名>
npm auditbash# a.js
const b = require('./b')
module.exports = 'a'
# b.js
const a = require('./a') # 此时拿到的是 b 里的空 exports
module.exports = 'b'CommonJS 的加载是同步递归的,循环引用时后加载的一方拿到的会是尚未填充完毕的 exports(可能是 {})。ESM 有hoisting 机制,能处理一部分循环,但仍会触发访问未初始化的绑定。能拆就拆开,循环依赖是设计问题不是绕得过去的技术限制。
同一需求下可以先试这些,尤其是不想买商业许可的时候。
代码编辑器 · 扩展 · 调试
写脚本、网页和多数语言都能先用它,扩展多、启动快。
Python · 脚本 · 数据分析
脚本、数据分析、爬虫和不少课程作业的默认语言。