在 Node.js 中运行代码
Node.js 是一个允许你在服务器端运行 JavaScript 的运行时环境。随着 TypeScript 的流行,Node.js 生态也提供了成熟的方案来直接运行 TypeScript 代码。
1. 运行 JavaScript (.js 文件)
这是最基本的操作。假设你有一个名为 app.js 的文件,你只需要在终端中执行以下命令:
Bash
node app.js
Node.js 会直接解释并执行该文件中的 JavaScript 代码。
2. 运行 TypeScript (.ts 文件)
Node.js 本身无法直接识别 TypeScript 语法。因此,我们需要借助一些工具将 TypeScript "实时"地转换为 JavaScript 来执行,或者先编译再执行。
方法一:使用 ts-node (推荐用于开发环境)
ts-node 是一个广受欢迎的工具,它可以在内存中将 TypeScript 即时编译为 JavaScript 并直接由 Node.js 执行,省去了手动编译的步骤,极大地提升了开发效率。
步骤:
安装依赖:
Bash
# 在你的项目中安装 typescript 和 ts-node npm install -D typescript ts-nodetypescript是ts-node的对等依赖 (peer dependency),必须安装。创建 tsconfig.json 文件:
ts-node 依赖 TypeScript 的配置文件来正确地编译代码。你可以通过以下命令生成一个默认的配置文件:
Bash
npx tsc --init这会创建一个
tsconfig.json文件,你可以根据项目需求进行修改。对于 Node.js 项目,建议将"module"选项设置为"NodeNext"或"CommonJS"。运行 .ts 文件:
假设你有一个 server.ts 文件,运行它就像运行 .js 文件一样简单:
Bash
npx ts-node server.ts
方法二:编译后运行 (推荐用于生产环境)
在生产环境中,为了获得最佳性能并减少不必要的依赖,标准的做法是先将所有 TypeScript 代码编译成 JavaScript,然后再用 Node.js 运行编译后的文件。
步骤:
安装 TypeScript:
Bash
npm install -D typescript配置 tsconfig.json:
确保 tsconfig.json 文件中的 outDir 选项被设置,它指定了编译后 .js 文件的输出目录。例如:
JSON
{ "compilerOptions": { "target": "es2020", "module": "NodeNext", "outDir": "./dist", // 指定输出目录为 dist "strict": true } }编译项目:
在终端中运行 TypeScript 编译器:
Bash
npx tsctsc会读取tsconfig.json的配置,并将项目中的所有.ts文件编译成.js文件,存放在./dist目录下。运行编译后的文件:
现在,你可以用标准的 Node.js 命令来运行你的应用了:
Bash
node dist/server.js
JavaScript 模块化语法:ESM vs. CJS
在 Node.js 中,如何组织和共享代码是通过模块系统来实现的。历史上,CommonJS 是 Node.js 的原生模块系统。后来,ECMAScript 标准定义了自己的模块系统,即 ES Modules。现代 Node.js 版本已经同时支持这两种系统。
1. CommonJS (CJS) - 传统模块系统
CJS 是 Node.js 诞生之初就采用的模块规范,它在服务端得到了广泛应用。它的模块加载是 同步 的。
核心语法:
导出模块 (module.exports 或 exports)
使用 module.exports 对象来导出一个模块的所有成员。
JavaScript
// math.js const PI = 3.14; function add(a, b) { return a + b; } class Calculator { // ... } // 导出一个包含多个成员的对象 module.exports = { PI, add, Calculator }; // 或者直接导出一个函数或类 // module.exports = add;exports是module.exports的一个引用或快捷方式。你可以用exports.add = ...,但不能直接给exports赋值(如exports = ...),因为这会切断它与module.exports的关联。导入模块 (require)
使用 require() 函数来导入一个模块。require 会同步地加载并执行模块文件,然后返回其 module.exports 对象。
JavaScript
// app.js const math = require('./math.js'); // 导入本地模块 const fs = require('fs'); // 导入内置模块 console.log(math.PI); // 3.14 console.log(math.add(2, 3)); // 5
特点:
同步加载:
require()会阻塞后续代码的执行,直到模块加载完成。这在服务端是可行的,因为文件通常在本地磁盘,读取速度很快。动态导入:
require的路径可以是变量,可以在代码的任何位置调用。缓存: 模块在第一次被
require后会被缓存,后续的require会直接从缓存中读取。.cjs文件: 在明确需要使用 CommonJS 的项目中,可以将文件后缀名改为.cjs。
2. ES Modules (ESM) - 官方标准模块系统
ESM 是由 ECMAScript 官方定义的标准模块化方案,它在浏览器和现代 Node.js 中都是首选。它的设计目标是 静态可分析,以支持摇树优化 (Tree Shaking) 等高级功能。
启用 ESM:
要在 Node.js 中使用 ESM,你有两种主要方式:
在 package.json 中声明:
这是最常见的方式。在 package.json 文件中添加顶级字段 "type": "module"。
JSON
{ "name": "my-esm-project", "version": "1.0.0", "type": "module" }一旦设置,项目中的所有
.js文件都会被 Node.js 当作 ESM 模块来处理。使用 .mjs 文件扩展名:
如果你不想修改 package.json,或者想在同一个项目中混合使用 CJS 和 ESM,你可以将需要作为 ESM 处理的文件命名为 .mjs 后缀。
核心语法:
导出模块 (export)
使用 export 关键字。它可以用于命名导出(Named Exports)和默认导出(Default Export)。
JavaScript
// logger.mjs // 命名导出 export const version = '1.0'; export function log(message) { console.log(message); } // 默认导出 (每个模块最多一个) export default class Logger { constructor(name) { this.name = name; } }导入模块 (import)
使用 import 关键字从其他模块导入成员。
JavaScript
// main.mjs import Logger, { version, log as printLog } from './logger.mjs'; // 导入默认和命名成员,并可以重命名 console.log('Version:', version); // Version: 1.0 printLog('Hello ESM!'); // Hello ESM! const myLogger = new Logger('main');
特点:
异步加载: ESM 的加载和解析是异步的,更适合网络环境(如浏览器)。
静态结构:
import和export必须在文件的顶层作用域使用,不能在条件语句或函数中。这种静态性使得工具可以在不执行代码的情况下分析模块依赖关系。导入路径: 导入本地文件时必须包含完整的文件名和扩展名(例如
./logger.mjs),或者目录索引(./lib/)。顶层
await: 在 ESM 模块中,你可以在顶层直接使用await关键字,而无需将其包裹在async函数中,这对于异步初始化非常方便。
总结对比