Nitro 脱胎于Nuxt3, 根据官方文档,Nitro 是下一代服务器工具包
,它是一种先进的工具集,用于创建和部署服务器。
Nitro App 项目目录
下面是一个最简单的 nitro
项目目录结构:
javascript
├─ nitro-app
│ ├─ nitro.config.ts
│ ├─ package.json
│ ├─ pnpm-lock.yaml
│ ├─ README.md
│ ├─ server
│ │ ├─ api
│ │ │ ├─ index.ts
│ │ │ └─ [name]
│ │ │ └─ [age].ts
│ │ ├─ middleware
│ │ │ └─ logger.ts
│ │ ├─ plugins
│ │ ├─ routes
│ │ │ ├─ hello
│ │ │ │ ├─ [...all].ts
│ │ │ │ └─ [name].ts
│ │ │ ├─ index.ts
│ │ │ ├─ test.dev.ts
│ │ │ └─ test.prod.ts
│ │ └─ utils
│ │ └─ sum.ts
│ └─ tsconfig.json
其中package.json
的内容如下:
json
{
"private": true,
"scripts": {
"build": "nitro build",
"dev": "nitro dev",
"prepare": "nitro prepare",
"preview": "node .output/server/index.mjs"
},
"devDependencies": {
"nitropack": "latest"
}
}
当我们执行pnpm dev
即 nitro dev
的时候,即可以启动开发服务器。
Nitro Dev 执行流程
当我们使用 pnpm dlx giget@latest nitro nitro-app --install
创建项目并安装依赖后。
会在项目的node_modules/.bin
目录生成nitro
和 nitropack
(nitro 是nitropack 的别名,功能一样 ) 可执行命令的符号链接。 这样我们就可以在项目目录直接执行命令。
当通过
npm install
或pnpm add
安装依赖时,如果某个包在其 package.json 中声明了 bin 字段, 则会将包内的可执行文件(如 bin/dist/cli/index.mjs)软连接到node_modules/.bin
目录。
在 Windows 中,会生成 .cmd 或 .ps1 脚本文件(用于兼容命令行调用)。
这样我们就可以在项目的 scripts字段中使用该命令,或 通过npx nitro
执行该命令。
实际上,我们查看nitro的源码,即可知,其package.json
中包即包含bin
字段:
json
{
"name": "nitro",
"version": "3.0.0-beta",
"bin": {
"nitro": "./dist/cli/index.mjs",
"nitropack": "./dist/cli/index.mjs"
}
}
即当执行nitro时,对应的打包后的文件即为 /dist/cli/index.mjs
,其代码如下:
javascript
#!/usr/bin/env node
import { defineCommand, runMain } from 'citty';
import { version } from 'nitropack/meta';
const main = defineCommand({
meta: {
name: "nitro",
description: "Nitro CLI",
version: version
},
subCommands: {
dev: () => import('./dev.mjs').then((r) => r.default),
build: () => import('./build.mjs').then((r) => r.default),
prepare: () => import('./prepare.mjs').then((r) => r.default),
task: () => import('./index2.mjs').then((r) => r.default)
}
});
runMain(main);
上述文件,对应的nitro
源码为:
javascript
// https://github.com/nitrojs/nitro/blob/v3/src/cli/index.ts
#!/usr/bin/env node
import { defineCommand, runMain } from "citty";
import { version as nitroVersion } from "nitro/meta";
const main = defineCommand({
meta: {
name: "nitro",
description: "Nitro CLI",
version: nitroVersion,
},
subCommands: {
dev: () => import("./commands/dev").then((r) => r.default),
build: () => import("./commands/build").then((r) => r.default),
prepare: () => import("./commands/prepare").then((r) => r.default),
task: () => import("./commands/task").then((r) => r.default),
},
});
runMain(main);
即,当我们执行nitro dev
时, 实际执行的文件为https://github.com/nitrojs/nitro/blob/v3/src/cli/commands/dev.ts
, 这样会启动一个具有热重载功能的开发服务器, 即在配置发生变化时能够重新加载服务器。
执行run方法
其核心逻辑是下面的 run
方法:
javascript
export default defineCommand({
meta: {
name: "dev",
description: "Start the development server",
},
args: {
...commonArgs,
...getArgs(),
},
async run({ args }) {
const rootDir = resolve((args.dir || args._dir || ".") as string);
let nitro: Nitro;
const reload = async () => {
// 关闭现有的 nitro 实例
// 如果 nitro 实例已经存在,首先输出信息提示正在重启开发服务器。
if (nitro) {
consola.info("Restarting dev server...");
if ("unwatch" in nitro.options._c12) {
await nitro.options._c12.unwatch();
}
await nitro.close();
}
// 创建新的 nitro 实例
// 第一个参数是 nitro 的配置对象,包括项目根目录、开发模式、预设和命令信息。
// 第二个参数是额外的配置选项,包括文件监听和配置更新的回调函数。
nitro = await createNitro(
{
rootDir,
dev: true,
preset: "nitro-dev",
_cli: { command: "dev" },
},
{
watch: true,
c12: {
// onUpdate 是一个异步回调函数,当 Nitro 配置发生变化时会被调用。
// 根据差异的情况决定是进行热更新还是完全重新加载
async onUpdate({ getDiff, newConfig }) {
const diff = getDiff();
if (diff.length === 0) {
return; // No changes
}
consola.info(
"Nitro config updated:\n" +
diff.map((entry) => ` ${entry.toString()}`).join("\n")
);
// 调用 nitro.updateConfig 方法进行热更新。
// 调用 reload 函数进行完全重新加载。
await (diff.every((e) => hmrKeyRe.test(e.key))
? nitro.updateConfig(newConfig.config || {}) // Hot reload
: reload()); // Full reload
},
},
}
);
// 监听 nitro 实例的 restart 事件,当该事件触发时,调用 reload 函数重新加载服务器。
nitro.hooks.hookOnce("restart", reload);
// 使用 createDevServer 函数创建一个开发服务器实例。
const server = createDevServer(nitro);
const listhenOptions = parseArgs(args);
const port =
listhenOptions.port ||
nitro.options.devServer.port ||
process.env.PORT ||
3000;
const hostname =
listhenOptions.hostname ||
nitro.options.devServer.hostname ||
process.env.HOST;
// 调用 server.listen 方法启动开发服务器。
await server.listen(port, {
...listhenOptions,
port,
hostname,
});
// prepare 函数用于准备 nitro 项目,例如创建必要的目录和文件。
// build 函数用于构建 nitro 项目。
await prepare(nitro);
await build(nitro);
};
//调用 reload 函数启动开发服务器
await reload();
},
});
创建Nitro Instance
createNitro
用于创建 nitro
实例, 其核心代码如下:
javascript
export async function createNitro(
config: NitroConfig = {},
opts: LoadConfigOptions = {}
): Promise<Nitro> {
// Resolve options
const options = await loadOptions(config, opts);
// Create nitro context
const nitro: Nitro = {
options,
hooks: createHooks(),
vfs: {},
logger: consola.withTag("nitro"),
scannedHandlers: [],
close: () => nitro.hooks.callHook("close"),
storage: undefined as any,
async updateConfig(config: NitroDynamicConfig) {
updateNitroConfig(nitro, config);
},
};
// 部分代码省略
return nitro;
}
加载配置文件
我们可以看到,在创建nitro instance
的时候,首先会解析和加载配置文件。
javascript
export async function loadOptions(
configOverrides: NitroConfig = {},
opts: LoadConfigOptions = {}
): Promise<NitroOptions> {
// 加载配置文件
const options = await _loadUserConfig(configOverrides, opts);
for (const resolver of configResolvers) {
await resolver(options);
}
return options;
}
其中,_loadUserConfig
定义了一个异步函数 ,其主要功能是加载并解析 Nitro 的用户配置,处理预设(preset)、兼容性日期等配置项,最终返回完整的 Nitro 配置选项。
在 _loadUserConfig
,nitro 使用c12
库加载用户的nitro.config.ts
。
具体加载规则,可以参考
c12
库的文档。
启动开发服务器
javascript
export function createDevServer(nitro: Nitro): NitroDevServer {
// 底层实际为 h3 库的实例
const devServer = new DevServer(nitro);
return {
reload: () => devServer.reload(),
listen: (port, opts) => devServer.listen(port, opts),
close: () => devServer.close(),
upgrade: (req, socket, head) => devServer.handleUpgrade(req, socket, head),
get app() {
return devServer.app;
},
get watcher() {
return devServer.watcher;
},
};
}
其中会涉及 node 的 worker 来处理请求。每个工作进程(worker)都在一个独立的Node.js进程中运行,以此来实现隔离。
具体源代码:https://github.com/nitrojs/nitro/blob/4b29402c/src/dev/worker.ts
开发服务器配置和中间层处理
Nitro
的中间层(middleware layers
) 主要包括:
- handlers 处理
- 虚拟文件(Virtual filesystem) 处理
- 静态资源 (Static asset )处理
- proxy 代理
- 请求处理
等等。
监控文件变化
最后nitro
还会监控文件变化,重启开发服务器。