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 devnitro dev 的时候,即可以启动开发服务器。

Nitro Dev 执行流程

当我们使用 pnpm dlx giget@latest nitro nitro-app --install 创建项目并安装依赖后。

会在项目的node_modules/.bin 目录生成nitronitropack (nitro 是nitropack 的别名,功能一样 ) 可执行命令的符号链接。 这样我们就可以在项目目录直接执行命令。

当通过 npm installpnpm 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 还会监控文件变化,重启开发服务器。