Vite と VitePress のつなぎ込み - dev 編

<!--
resolver 更新
composable がこのようになっていたとは...
https://github.com/vuejs/vitepress/search?q=exports.ts&unscoped_q=exports.ts
  -->

## はじめに

* バージョン ... 0.31
* SHA-1 ハッシュ ... 7519f1f6e356a5521404116aba57009d123d6572

1. [VitePress のディレクトリ構成](https://qiita.com/domodomodomo/items/1048f51d98ab5fd52d12)
2. [src/client/app と src/client/theme-default のつなぎ込み](https://qiita.com/domodomodomo/items/49b1944f09f8f9f57519)
3. [Markdown ファイルと src/client/app, Vue インスタンス本体のつなぎ込み](https://qiita.com/domodomodomo/items/f44bf1f49cff511890c7)
4. [Vite と VitePress のつなぎ込み](https://qiita.com/domodomodomo/items/c3b828bf9f7cb7e9a18f)
  1. [dev 編  <-- いまココです。](https://qiita.com/domodomodomo/items/aea100ae69344d6dcc39)
  2. build 編(未着手)

## 概要

### ◯ Vite と VitePress のつなぎこみ
Vite 向けの VitePress のプラグインを噛ませつなぎこみを行なっています。Vite はプラグインが多段に組まれていています。後述します。また、以下のスライドの中に図示されている箇所があるので、雰囲気が、つかめるかもしれません。

* [Native-ESM powered web dev build tool - speakerdeck](https://speakerdeck.com/kazupon/native-esm-powered-web-dev-build-tool)
* [Native-ESM powered web dev build tool - YouTube](https://www.youtube.com/watch?v=rx249I9JmNU&feature=youtu.be&t=460)

具体的に VitePress のプラグインを噛ませているのは Step 2 になります。ここでは主に VitePress のプラグインを見ていきます。

### ◯ 通信

例えば、いま `http://127.0.0.1:3000/hello.html` にアクセスしたとします。Chrome のデベロッパツールを開いて確認するとわかりやすいかもしれません。動画作りました。

[![IMAGE ALT TEXT HERE](http://img.youtube.com/vi/7TtBTTgwviI/0.jpg)](http://www.youtube.com/watch?v=7TtBTTgwviI)



```html:inde.html
<div id="app"></div>
<script type="module" src="/@app/index.js"></script>
```

* [vuejs/vitepress - /src/client/app/index.html](https://github.com/vuejs/vitepress/blob/7519f1f6e356a5521404116aba57009d123d6572/src/client/app/index.html)

`/@app/index.js` が呼び出されます。これは `/src/client/app/index.js` です。

* [vuejs/vitepress - /src/client/app/index.js](https://github.com/vuejs/vitepress/blob/7519f1f6e356a5521404116aba57009d123d6572/src/client/app/index.ts)

パス変換のロジックが記述されている箇所は、以下をご確認ください。resolver のあたりは、あまりよくわかっていません。

* [vuejs/vitepress - /src/node/resolver.ts](https://github.com/vuejs/vitepress/blob/7519f1f6e356a5521404116aba57009d123d6572/src/node/resolver.ts)

`/src/client/app/index.js` は Vue.js のインスタンスを作成して、そのインスタンスをマウントしていることが確認できます。

```typescript:/src/client/app/index.js#L112-L118
if (inBrowser) {
  // 1) Vue.js のインスタンスを作成
  const { app, router } = createApp()

  // 2) そのインスタンスをマウント
  // wait unitl page component is fetched before mounting
  router.go().then(() => {
    app.mount('#app')
  })
}
```
* [vuejs/vitepress - /src/client/app/index.js#L112-L118](https://github.com/vuejs/vitepress/blob/7519f1f6e356a5521404116aba57009d123d6572/src/client/app/index.ts#L112-L118)


さらに router.go の中を覗くと location.href に遷移していることがわかります。

```typescript:/src/client/app/router.ts#L28-L36
  function go(href?: string) {
    // location.href で対象のコンポーネントを読み込みます。
    href = href || (inBrowser ? location.href : '/')

    if (inBrowser) {
      // save scroll position before changing url
      history.replaceState({ scrollPosition: window.scrollY }, document.title)
      history.pushState(null, '', href)
    }
    return loadPage(href)
  }
```

* [vuejs/vitepress - /src/client/app/router.ts#L28-L36](https://github.com/vuejs/vitepress/blob/da4852a61bd73a8b46c4971c330f95761237c733/src/client/app/router.ts#L28-L36)

ルータの実装については、以下の記事で詳細を後述します。

* [VitePress のルータの実装について - Qiita](https://qiita.com/domodomodomo/items/f44bf1f49cff511890c7)

## Step 1. VitePress - エントリーポイント

yarn dev, yarn build を実行すると、ここから起動します。

```/bin/vitepress.js
//
// こっちが実行されます。dev
//
if (!command || command === 'dev') {
  const port = argv.port || 3000
  const root = command === 'dev' && argv._[1]
  if (root) {
    argv.root = root
  }
  require('../dist/node')  // <--- ../src/node を参照します。
    .createServer(argv)
    .then((server) => {
      server.listen(port, () => {
        console.log(`listening at http://localhost:${port}`)
      })
    })
    .catch((err) => {
      console.error(chalk.red(`failed to start server. error:\n`), err)
    })
//
// こっちじゃない。build
//
} else if (command === 'build') {
  require('../dist/node')
    .build(argv)
    .catch((err) => {
      console.error(chalk.red(`build error:\n`), err)
    })
} else {
  console.log(chalk.red(`unknown command "${command}".`))
}
```

* [vuejs/vitepress - /bin/vitepress.js#L10-L34](https://github.com/vuejs/vitepress/blob/7519f1f6e356a5521404116aba57009d123d6572/bin/vitepress.js#L10-L34)



## Step 2. VitePress - createServer

Step 1 の `createServer` は、以下のように定義されています。Step 1 の実引数 `argv` は、Step 2 の仮引数 `options` に代入されます。VitePress は Vite のプラグインとして、実装されていることがわかります。

```typescript:/src/node/server.ts#L137-L145
export async function createServer(options: ServerConfig = {}) {
  const config = await resolveConfig(options.root)
  
  return createViteServer({                   // `vite` から import した `createServer` です。
    ...options,                               // Step 1 のコマンドライン引数 `argv` をアンパックしています。
    plugins: [createVitePressPlugin(config)], // ポイント ... これが VitePress の本体です。
    resolvers: [config.resolver]              // resolvers は、後述します。
  })
}
```

* [vue/vitepress - /src/server.ts#L137-L145 - vue/vitepress](https://github.com/vuejs/vitepress/blob/7519f1f6e356a5521404116aba57009d123d6572/src/node/server.ts#L126-L134)

`createViteServer` は `vite` から import した `createServer` です。

```javascript:/src/server.ts#L2-L7
import {
  createServer as createViteServer, // <--- ココ
  cachedRead,
  ServerConfig,
  ServerPlugin
} from 'vite'
```

* [vue/vitepress - /src/server.ts#L2-L7 - vue/vitepress](https://github.com/vuejs/vitepress/blob/7519f1f6e356a5521404116aba57009d123d6572/src/node/server.ts#L2-L7)

また、ここでのポイントは VitePress の本体に当たる部分が Vite Server の Plugin として渡されていることです。これは追って見ていきます。

```javascript
    plugins: [createVitePressPlugin(config)], // ポイント ... これが VitePress の本体です。
```



### ◯ resolveConfig

resolveConfig って、何してるのかな？と思ったのですが、サイトのメタデータを作成していました。resolve というよりは create なイメージでした。

```javascript
export async function resolveConfig(
  root: string = process.cwd()
): Promise<SiteConfig> {
  const site = await resolveSiteData(root)


  // resolve theme path
  const userThemeDir = resolve(root, 'theme')
  const themeDir = (await exists(userThemeDir))
    ? userThemeDir
    : path.join(__dirname, '../lib/theme-default')


  const config: SiteConfig = {
    root,
    site,
    themeDir,
    pages: await globby(['**.md'], { cwd: root, ignore: ['node_modules'] }),
    configPath: resolve(root, 'config.js'),
    publicDir: resolve(root, 'public'),
    outDir: resolve(root, 'dist'),
    tempDir: path.resolve(APP_PATH, 'temp'),
    resolver: createResolver(themeDir)
  }


  return config
}
```
* [vuejs/vitepress - /src/config.ts#L54-L78](https://github.com/vuejs/vitepress/blob/a3df035babe28fd086098fea26087fd3fbca17d0/src/config.ts#L54-L78)


また、どこで md ファイルを収集しているか？疑問だったのですが、ここで `config` の `page` プロパティに注目すると globby を使って Markdown ファイルのパスを集めていることが確認できます。

* [グロブ - Wikipedia](https://ja.wikipedia.org/wiki/%E3%82%B0%E3%83%AD%E3%83%96)

<!--
型定義は /src/config.ts の中にある。
  -->





## Step 3. VitePress - createVitePressPlugin

2 つの `watcher.on` と 1 つの `app.use` をしています。`watcher.on` はホットリロードの対象を登録しています。`app.use` は Koa の middleware を登録しています。`app` が Vue の app ではなく Koa の `app` であることに留意してください。

```typescript:/src/node/server.ts#L15-L124
function createVitePressPlugin({
  configPath,
  site: initialSiteData
}: SiteConfig): ServerPlugin {
  return ({ app, root, watcher, resolver }) => {
    const markdownToVue = createMarkdownToVueRenderFn(root)

    //
    // 記事となる Markdown ファイルの変更を監視登録しています。
    //
    // hot reload .md files as .vue files
    watcher.on('change', async (file) => {
      //
      // 後述 1
      //
    })

    //
    // 設定ファイルの変更を監視登録しています。
    //
    // hot reload handling for siteData
    // the data is stringified twice so it is sent to the client as a string
    // it is then parsed on the client via JSON.parse() which is faster than
    // parsing the object literal as JavaScript.
    let siteData = initialSiteData
    let stringifiedData = JSON.stringify(JSON.stringify(initialSiteData))
    watcher.add(configPath)
    watcher.on('change', async (file) => {
      //
      // 後述 2
      //
    })


    // inject Koa middleware
    app.use(async (ctx, next) => {
      //
      // 後述 3
      //
    })
  }
}
```

* [vue/vitepress - /src/node/server.ts#L15-L124 - vue/vitepress](https://github.com/vuejs/vitepress/blob/7519f1f6e356a5521404116aba57009d123d6572/src/node/server.ts#L15-L124)



### ◯ 後述 1 

Markdown ファイルの更新を監視するコードが書かれています。こんなに繊細に書かれているとは思いませんでした。もっとざっくりとディレクトリ配下は全部監視！みたいなのかと思っていました...orz

```javascript
    // hot reload .md files as .vue files
    watcher.on('change', async (file) => {
      if (file.endsWith('.md')) {
        debugHmr(`reloading ${file}`)
        //
        // cachedRead
        //    キャッシュを読み込みます。
        //    キャッシュってなんだろうと思ってコードを追ってみたのですが、
        //    このキャッシュは、メモリからのキャッシュです。
        //    ファイルが読み込まれていなければ fs.open() します。
        //
        const content = await cachedRead(null, file)

        // Vite では timestamp をキャッシュを更新するか、
        // しないかの判断に使うことがあるそうです。
        // このコードブロックの末尾を確認すると雰囲気がわかります。
        //   watcher.handleVueReload(file, timestamp, vueSrc)
        const timestamp = Date.now()

        //
        // Markdown ファイルが Vue の SFC の template に変換されます。
        //
        const { pageData, vueSrc } = markdownToVue(
          content,
          file,
          timestamp,
          // do not inject pageData on HMR
          // it leads to vite to think <script> has changed and reloads the
          // component instead of re-rendering.
          // pageData needs separate HMR logic anyway (see below)
          false
        )


        // notify the client to update page data
        watcher.send({
          type: 'custom',
          id: 'vitepress:pageData',
          customData: {
            path: resolver.fileToRequest(file),
            pageData
          },
          timestamp: Date.now()
        })


        // reload the content component
        watcher.handleVueReload(file, timestamp, vueSrc)
      }
    })
```

* [vitepress/src/node/server.ts#L22-L53](https://github.com/vuejs/vitepress/blob/7519f1f6e356a5521404116aba57009d123d6572/src/node/server.ts#L22-L53)

### ◯ 後述 2
省略

### ◯ 後述 3 - inject Koa middleware とは

上記コードのコメントに "inject Koa middleware" という文言が見えます。Koa の middleware は、簡単に言えばフックです。HTTP リクエストをフックして処理を追加します。

* [フック(hook) - 「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典](https://wa3.i-3-i.info/word12296.html)

`app.use(async (ctx, next) => { ... }` に着目していきます。ctx には HTTP リクエストの内容が、next には次に控えている middleware を呼び出す callback 関数が代入されています。await next() すると次の middleware に操作が移り、return されると戻って来ます。

Vite の middleware 間の関係は、わかっていません。`await next()` にかかる挙動は、以下 Developers.IO の記事がわかりやすいです。

* [koa v2 によるAPIアプリケーションの実装例 - Developers.IO](https://dev.classmethod.jp/articles/koa-v2-sample/)
* [Why do we await next when using koa routers? - Stackoverflow](https://stackoverflow.com/questions/42532534/why-do-we-await-next-when-using-koa-routers)
* [Mastering Koa Middleware - Netscape](https://medium.com/netscape/mastering-koa-middleware-f0af6d327a69) 

"inject Koa middleware" 以下の内容を抜粋しました。３種類のファイルをフックして、処理を加えていることがわかります。

```typescript:/src/node/server.ts#L76-L122
    // inject Koa middleware
    app.use(async (ctx, next) => {
      //
      // 1) @siteDate の場合
      //    サイトのタイトルなどのメタデータが保存されています。
      //
      // serve siteData (which is a virtual file)
      if (ctx.path === SITE_DATA_REQUEST_PATH) {
        ctx.type = 'js'
        ctx.body = `export default ${stringifiedData}`
        debug(ctx.url)
        return
      }

      //
      // 2) Markdown ファイルの場合
      //    例えば、動画の最後でダウンロードされた hello.md ファイルが該当します。
      //
      // handle .md -> vue transforms
      if (ctx.path.endsWith('.md')) {
        //
        // /index.md -> docs/index.md に変換します。
        // resolver については、後述します。
        const file = resolver.requestToFile(ctx.path)

        // cachedRead
        //   ctx.body にファイルの中身が設定されます。
        //
        await cachedRead(ctx, file)

        // let vite know this is supposed to be treated as vue file
        ctx.vue = true


        const { vueSrc, pageData } = markdownToVue(
          ctx.body,
          file,
          ctx.lastModified.getTime(),
          false
        )
        ctx.body = vueSrc
        debug(ctx.url, ctx.status)


        await next()


        // make sure this is the main <script> block
        if (!ctx.query.type) {
          // inject pageData to generated script
          ctx.body += `\nexport const __pageData = ${JSON.stringify(
            JSON.stringify(pageData)
          )}`
        }
        return
      }


      await next()

      //
      // 3) 動画の最初にダウンロードされた hello.html
      //    全て index.html が返されます。
      //    router.go で hello.md を表示しています。
      //    SPA のような動作をしています。
      //

      // serve our index.html after vite history fallback
      if (ctx.url.endsWith('.html')) {
        await cachedRead(ctx, path.join(APP_PATH, 'index.html'))
        ctx.status = 200
      }
    })
```

* [vue/vitepress - /src/node/server.ts#L76-L122](https://github.com/vuejs/vitepress/blob/7519f1f6e356a5521404116aba57009d123d6572/src/node/server.ts#L76-L122)



TODO:
`cachedRead`, `cachedRead` といった文言が見え、Markdown ファイルを読んでいるのは、わかるが、どこで app と繋がっているかわからないので、調べる。


<br><br><br><br>

**ここから Vite のコードを追いかけます。**



## Step 4. Vite - createServer

Vite のコードを読むと大量の Vite のプラグインが登録されていることを確認できます。登録の順番は、引数で追加したもの -> Vite が最初からつけているもの、となっているようです。

1. `koa` のインスタンスを作りサーバを走らせる。
2. `chokidar` で hot reload を有効にする。
3. 何をしているのか、わからない。
4. 引数で渡された plugins を実行する。

```/src/node/server/index.ts
import { vuePlugin } from './serverPluginVue' // <--- 後述します。

// ... 中略

const internalPlugins: Plugin[] = [
  hmrPlugin,
  moduleRewritePlugin,
  moduleResolvePlugin,
  vuePlugin,  // <--- 後述します。
  esbuildPlugin,
  jsonPlugin,
  cssPlugin,
  assetPathPlugin,
  serveStaticPlugin
]

export function createServer(config: ServerConfig = {}): Server {
  const {
    root = process.cwd(),
    plugins = [],
    resolvers = [],
    jsx = {}
  } = config

  //
  // 1) app を生成して create サーバに渡す。
  //   ここでの app は Vue のインスタンスではなく、
  //   Koa のインスタンスを参照していることに留意したい。
  //
  const app = new Koa()
  const server = http.createServer(app.callback())

  //
  // 2) `/node_modules/` 以外に hot reload がかかるように登録している。
  //
  const watcher = chokidar.watch(root, {
    ignored: [/node_modules/]
  }) as HMRWatcher

  //
  // 3) 何をしているのか、わからない。
  //
  const resolver = createResolver(root, resolvers)

  //
  // 4) createServer を実行したタイミングで
  // plugins を実行していることがわかる。
  // 
  const context = {
    root,
    app,
    server,
    watcher,
    resolver,
    jsxConfig: {
      jsxFactory: jsx.factory,
      jsxFragment: jsx.fragment
    }
  }
  ;[...plugins, ...internalPlugins].forEach((m) => m(context))


  return server
}
```

* [vitejs/vite - /src/node/server/index.ts#L41-L81](https://github.com/vitejs/vite/blob/eab49a4b7dd7e3bb0ff215c7e7937814cd63bb4f/src/node/server/index.ts#L41-L81)

以下のディレクトリの配下に internalPlugins が保存されています。一覧すると雰囲気がつかめるかもしれません。

* [vitejs/vite - /src/node/server](https://github.com/vitejs/vite/tree/cf5de5b131beeab33eff942b288772f991112eea/src/node/server)



## Step 5. Vite - serverPluginVue

serverPluginVue を覗くと `compileSFCTemplate`, `compileSFCStyle` という文言が見えます。ここで SFC, Single File Component を変換, compile しているようです。

```typescript:/src/node/server/serverPluginVue.ts#L94-L109
    if (query.type === 'template') {
      const templateBlock = descriptor.template!
      if (templateBlock.src) {
        filename = await resolveSrcImport(templateBlock, ctx, resolver)
      }

      // ... 中略

      return etagCacheCheck(ctx)
    }
   
    if (query.type === 'style') {
      const index = Number(query.index)
      const styleBlock = descriptor.styles[index]
      if (styleBlock.src) {
        filename = await resolveSrcImport(styleBlock, ctx, resolver)
      }
      const id = hash_sum(publicPath)
      const result = await compileSFCStyle(
        root,
        styleBlock,
        index,
        filename,
        publicPath
      )
      ctx.type = 'js'
      ctx.body = codegenCss(`${id}-${index}`, result.code, result.modules)
      return etagCacheCheck(ctx)
    }
```

* [/vitejs/vite - /src/node/server/serverPluginVue.ts#L94-L131](https://github.com/vitejs/vite/blob/cf5de5b131beeab33eff942b288772f991112eea/src/node/server/serverPluginVue.ts#L94-L131)





<br><br><br><br>

**ここから resolver を追いかけます。**


## Step 6. VitePress - resolver

VitePress の中で resolve, resolver という単語は、色々な意味で使われますが、Vite 本体に渡す resolver について見て行きます。Vite 本体に渡す resolver は HTTP リクエストのパスを、ファイルシステムのどのパスに対応づけるかを記述します。

例えば、以下のように docs ディレクトリを指定した場合...

```
$ npx vitepress dev docs
```

HTTP リクエストは以下のように変換されて欲しいです。

```
/             ->  /docs/index.md
/hello        ->  /docs/hello.md
/hello/nihao  ->  /docs/hello/nihao.md
```


該当箇所を引用しつつ resolvers に何が代入されているかを追いかけに行きます。

```typescript:/src/node/server.ts#L137-L145
export async function createServer(options: ServerConfig = {}) {
  const config = await resolveConfig(options.root) // <--- ここに注目してください。
  
  return createViteServer({
    ...options,
    plugins: [createVitePressPlugin(config)],
    resolvers: [config.resolver] // <--- ここに注目してください。
  })
}
```

* [vue/vitepress - /src/server.ts#L137-L145 - vue/vitepress](https://github.com/vuejs/vitepress/blob/7519f1f6e356a5521404116aba57009d123d6572/src/node/server.ts#L126-L134)

config を作成している resolveConfig　を見て行きます。

```javascript
export async function resolveConfig(
  root: string = process.cwd()
): Promise<SiteConfig> {
  const site = await resolveSiteData(root)


  // resolve theme path
  const userThemeDir = resolve(root, 'theme')
  const themeDir = (await exists(userThemeDir))
    ? userThemeDir
    : path.join(__dirname, '../lib/theme-default')


  const config: SiteConfig = {
    root,
    site,
    themeDir,
    pages: await globby(['**.md'], { cwd: root, ignore: ['node_modules'] }),
    configPath: resolve(root, 'config.js'),
    publicDir: resolve(root, 'public'),
    outDir: resolve(root, 'dist'),
    tempDir: path.resolve(APP_PATH, 'temp'),
    resolver: createResolver(themeDir) // <--- ここに注目してください。
  }


  return config
}
```
* [vuejs/vitepress - /src/config.ts#L54-L78](https://github.com/vuejs/vitepress/blob/a3df035babe28fd086098fea26087fd3fbca17d0/src/config.ts#L54-L78)

resolver.js 全体を貼り付けます。

```javascript:/src/node/resolver.ts
import path from 'path'
import { Resolver } from 'vite'


export const APP_PATH = path.join(__dirname, '../client/app')


// special virtual file
// we can't directly import '/@siteData' becase
// - it's not an actual file so we can't use tsconfig paths to redirect it
// - TS doesn't allow shimming a module that starts with '/'
export const SITE_DATA_ID = '@siteData'
export const SITE_DATA_REQUEST_PATH = '/' + SITE_DATA_ID


// これは vite に渡されるパスリゾルバで、/@app や /@theme で始まる
// カスタムリクエストを解決することができます。
// また、vite HMR が正しい更新通知をクライアントに送信できるように、
// ファイルのパスをパブリックにサーブされたパスにマップする必要があります。
// this is a path resolver that is passed to vite
// so that we can resolve custom requests that start with /@app or /@theme
// we also need to map file paths back to their public served paths so that
// vite HMR can send the correct update notifications to the client.
export function createResolver(themeDir: string): Resolver {
  return {
    alias: {
      // このようにして /@app/ /@theme/ などがある理由は、
      // Vue のインスタンスや theme の SFC が個別にダウンロードされるからです。
      // (うまい説明が思いつきません... orz)
      // デベロッパツールのネットワークを開き、
      // @app や @theme へのリクエストの中身を見ると雰囲気がわかるかもしれません。
      '/@app/': APP_PATH,
      '/@theme/': themeDir,
      vitepress: '/@app/exports.js',
      [SITE_DATA_ID]: SITE_DATA_REQUEST_PATH
    },
    //
    // HTTP リクエストから -> サーバのファイルパスへの変換
    //
    requestToFile(publicPath) {
      if (publicPath === SITE_DATA_REQUEST_PATH) {
        return SITE_DATA_REQUEST_PATH
      }
    },
    //
    // サーバのファイルパスから -> HTTP リクエストへの変換
    //
    fileToRequest(filePath) {
      if (filePath === SITE_DATA_REQUEST_PATH) {
        return SITE_DATA_REQUEST_PATH
      }
    }
    // ここでなぜ requestToFile, fileToRequest が
    // このような書き方をされているのか、まだつかめていません。
  }
}
```

* [vuejs/vitepress - /src/node/resolver.ts#L1-L36](https://github.com/vuejs/vitepress/blob/903b34c6171c976d367abb1aa9c795ebe182d336/src/node/resolver.ts#L1-L36)


## Step 7. Vite - resolver

次は Vite の resolver を見て行きます。Step 6 の createViteServer を思い出してください。

```typescript:/src/node/server/index.ts#L53-L81
export function createServer(config: ServerConfig = {}): Server {
  const {
    root = process.cwd(),
    plugins = [],
    resolvers = [],  // <--- ここに注目してください。
    jsx = {}
  } = config
  const app = new Koa()
  const server = http.createServer(app.callback())
  const watcher = chokidar.watch(root, {
    ignored: [/node_modules/]
  }) as HMRWatcher
  const resolver = createResolver(root, resolvers)  // <--- ここに注目してください。
  const context = {
    root,
    app,
    server,
    watcher,
    resolver, // <--- ここに注目してください。
    jsxConfig: {
      jsxFactory: jsx.factory,
      jsxFragment: jsx.fragment
    }
  }


  ;[...plugins, ...internalPlugins].forEach((m) => m(context))


  return server
}
```

* [vuejs/vitepress - /src/node/server/index.ts#L53-L81](https://github.com/vitejs/vite/blob/eab49a4b7dd7e3bb0ff215c7e7937814cd63bb4f/src/node/server/index.ts#L53-L81)

`const resolver = createResolver(root, resolvers)` を見ていきます。引数に注目します。

`root` にはドキュメント、マークダウンファイルの保存されたディレクトリの root が代入されます。例えば docs です。

`resolvers` には、VitePress から呼び出す時は、Step6 で見てきた resolvers が代入されています。コードを見ると、カスタムの resolver が該当しなければデフォルトの resolver で解決するということをしているようです。

```typescript:/src/node/resolver.ts#L79-L114
export function createResolver(
  root: string,
  resolvers: Resolver[]
): InternalResolver {
  return {
    requestToFile: (publicPath) => {
      let resolved: string | undefined
      for (const r of resolvers) {
        const filepath = r.requestToFile(publicPath, root)
        if (filepath) {
          resolved = filepath
          break
        }
      }
      if (!resolved) {
        resolved = defaultRequestToFile(publicPath, root)
      }
      resolved = resolveExt(resolved)
      return resolved
    },
    fileToRequest: (filePath) => {
      for (const r of resolvers) {
        const request = r.fileToRequest(filePath, root)
        if (request) return request
      }
      return defaultFileToRequest(filePath, root)
    },
    idToRequest: (id: string) => {
      for (const r of resolvers) {
        const request = r.idToRequest && r.idToRequest(id)
        if (request) return request
      }
      return defaultIdToRequest(id)
    }
  }
}
```

* [vuejs/vitepress - /src/node/resolver.ts#L79-L114](https://github.com/vitejs/vite/blob/eab49a4b7dd7e3bb0ff215c7e7937814cd63bb4f/src/node/resolver.ts#L79-L114)


## おわりに

以上になります。ありがとうございました。build 編は、まだ未着手です。